feat: add indexes and optimize list performance
This commit is contained in:
@@ -90,7 +90,7 @@
|
||||
</slot>
|
||||
</span>
|
||||
</span>
|
||||
<span class="email-content">{{ item.formatText || '\u200B' }}</span>
|
||||
<span class="email-content">{{ item.text || '\u200B' }}</span>
|
||||
</div>
|
||||
<div class="user-info" v-if="showUserInfo">
|
||||
<div class="user">
|
||||
@@ -554,37 +554,6 @@ const accountShow = computed(() => {
|
||||
return uiStore.accountShow && settingStore.settings.manyEmail === 0
|
||||
})
|
||||
|
||||
function htmlToText(email) {
|
||||
if (email.content) {
|
||||
|
||||
const tempDiv = document.createElement('div');
|
||||
|
||||
tempDiv.innerHTML = email.content.replace(
|
||||
/<(img|iframe|object|embed|video|audio|source|link)[^>]*>/gi, ''
|
||||
);
|
||||
|
||||
const scriptsAndStyles = tempDiv.querySelectorAll('script, style, title');
|
||||
scriptsAndStyles.forEach(el => el.remove());
|
||||
let text = tempDiv.textContent || tempDiv.innerText || '';
|
||||
text = text.replace(/\s+/g, ' ').trim();
|
||||
return cleanSpace(text)
|
||||
}
|
||||
|
||||
if (email.text) {
|
||||
return cleanSpace(email.text)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function cleanSpace(text) {
|
||||
return text
|
||||
.replace(/[\u200B-\u200F\uFEFF\u034F\u200B-\u200F\u00A0\u3000\u00AD]/g, '')// 移除零宽空格
|
||||
.replace(/\s+/g, ' ') // 多空白合并成一个空格
|
||||
.trim();
|
||||
}
|
||||
|
||||
function starChange(email) {
|
||||
|
||||
if (!email.isStar) {
|
||||
@@ -734,7 +703,6 @@ function addItem(email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
email.formatText = htmlToText(email);
|
||||
email.formatCreateTime = fromNow(email.formatCreateTime);
|
||||
|
||||
if (props.timeSort) {
|
||||
@@ -888,7 +856,6 @@ function getEmailList(refresh = false) {
|
||||
|
||||
function handleList(list) {
|
||||
list.forEach(email => {
|
||||
email.formatText = htmlToText(email)
|
||||
email.formatCreateTime = fromNow(email.createTime);
|
||||
email.test = t('received')
|
||||
const statusIconMap = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import http from '@/axios/index.js';
|
||||
|
||||
export function emailList(accountId, allReceive, emailId, timeSort, size, type) {
|
||||
return http.get('/email/list', {params: {accountId, allReceive, emailId, timeSort, size, type}})
|
||||
export function emailList(accountId, allReceive, emailId, timeSort, size, type, full) {
|
||||
return http.get('/email/list', {params: {accountId, allReceive, emailId, timeSort, size, type, full}})
|
||||
}
|
||||
|
||||
export function emailDelete(emailIds) {
|
||||
|
||||
@@ -8,6 +8,6 @@ export function starCancel(emailId) {
|
||||
return http.delete('/star/cancel', {params: {emailId}})
|
||||
}
|
||||
|
||||
export function starList(emailId,size) {
|
||||
return http.get('/star/list', {params: {emailId,size}})
|
||||
export function starList(emailId,size,full) {
|
||||
return http.get('/star/list', {params: {emailId,size,full}})
|
||||
}
|
||||
@@ -15,8 +15,46 @@ export const useEmailStore = defineStore('email', {
|
||||
showUnread: false
|
||||
},
|
||||
sendScroll: null,
|
||||
detailMap: {},
|
||||
}),
|
||||
persist: {
|
||||
pick: ['contentData'],
|
||||
},
|
||||
actions: {
|
||||
fetchList(request) {
|
||||
request(1).then(data => {
|
||||
const list = Array.isArray(data) ? data : data?.list
|
||||
this.applyFullList(list)
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
return request(0)
|
||||
},
|
||||
applyFullList(list) {
|
||||
if (!list?.length) return
|
||||
const currentId = this.contentData.email?.emailId
|
||||
for (const item of list) {
|
||||
if (!item?.emailId) continue
|
||||
if (!item.attList) item.attList = []
|
||||
this.detailMap[item.emailId] = item
|
||||
if (currentId && item.emailId === currentId) {
|
||||
this.contentData.email = item
|
||||
}
|
||||
}
|
||||
},
|
||||
toContentEmail(email) {
|
||||
const id = email?.emailId
|
||||
if (id && this.detailMap[id]) {
|
||||
return this.detailMap[id]
|
||||
}
|
||||
return {
|
||||
...email,
|
||||
emailId: id || 0,
|
||||
content: '',
|
||||
text: '',
|
||||
attList: [],
|
||||
recipient: email?.recipient || '[]',
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -282,7 +282,7 @@ function typeSelectChange() {
|
||||
}
|
||||
|
||||
function jumpContent(email) {
|
||||
emailStore.contentData.email = email
|
||||
emailStore.contentData.email = emailStore.toContentEmail(email)
|
||||
emailStore.contentData.delType = 'physics'
|
||||
emailStore.contentData.showStar = false
|
||||
emailStore.contentData.showReply = false
|
||||
@@ -291,7 +291,7 @@ function jumpContent(email) {
|
||||
|
||||
|
||||
function getEmailList(emailId, size) {
|
||||
return allEmailList({emailId, size, ...params})
|
||||
return emailStore.fetchList(full => allEmailList({emailId, size, full, ...params}))
|
||||
}
|
||||
|
||||
async function latest() {
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
<el-alert v-if="email.status === 4" :closable="false" :title="$t('complained')" class="email-msg" type="warning" show-icon />
|
||||
<el-alert v-if="email.status === 5" :closable="false" :title="$t('delayed')" class="email-msg" type="warning" show-icon />
|
||||
</div>
|
||||
<el-scrollbar class="htm-scrollbar" :class="email.attList.length === 0 ? 'bottom-distance' : ''">
|
||||
<el-scrollbar class="htm-scrollbar" :class="!email.attList?.length ? 'bottom-distance' : ''">
|
||||
<ShadowHtml class="shadow-html" :html="formatImage(email.content)" v-if="email.content" />
|
||||
<pre v-else class="email-text" >{{email.text}}</pre>
|
||||
</el-scrollbar>
|
||||
<div class="att" v-if="email.attList.length > 0">
|
||||
<div class="att" v-if="email.attList?.length > 0">
|
||||
<div class="att-title">
|
||||
<span>{{$t('attachments')}}</span>
|
||||
<span>{{$t('attCount',{total: email.attList.length})}}</span>
|
||||
@@ -75,7 +75,7 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import ShadowHtml from '@/components/shadow-html/index.vue'
|
||||
import {reactive, ref, watch, onMounted, onUnmounted} from "vue";
|
||||
import {computed, reactive, ref, watch, onMounted, onUnmounted} from "vue";
|
||||
import {useRouter} from 'vue-router'
|
||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||
import {emailDelete, emailRead} from "@/request/email.js";
|
||||
@@ -98,7 +98,13 @@ const settingStore = useSettingStore();
|
||||
const accountStore = useAccountStore();
|
||||
const emailStore = useEmailStore();
|
||||
const router = useRouter()
|
||||
const email = emailStore.contentData.email
|
||||
const email = computed(() => emailStore.contentData.email || {
|
||||
emailId: 0,
|
||||
attList: [],
|
||||
content: '',
|
||||
text: '',
|
||||
recipient: '[]',
|
||||
})
|
||||
const showPreview = ref(false)
|
||||
const srcList = reactive([])
|
||||
|
||||
@@ -108,9 +114,9 @@ watch(() => accountStore.currentAccountId, () => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (emailStore.contentData.showUnread && email.unread === EmailUnreadEnum.UNREAD) {
|
||||
email.unread = EmailUnreadEnum.READ;
|
||||
emailRead([email.emailId]);
|
||||
if (emailStore.contentData.showUnread && email.value.unread === EmailUnreadEnum.UNREAD && email.value.emailId) {
|
||||
email.value.unread = EmailUnreadEnum.READ;
|
||||
emailRead([email.value.emailId]);
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
})
|
||||
@@ -130,11 +136,11 @@ function handleKeyDown(event) {
|
||||
}
|
||||
|
||||
function openReply() {
|
||||
uiStore.writerRef.openReply(email)
|
||||
uiStore.writerRef.openReply(email.value)
|
||||
}
|
||||
|
||||
function openForward() {
|
||||
uiStore.writerRef.openForward(email)
|
||||
uiStore.writerRef.openForward(email.value)
|
||||
}
|
||||
|
||||
function toMessage(message) {
|
||||
@@ -160,32 +166,33 @@ function isImage(filename) {
|
||||
}
|
||||
|
||||
function formateReceive(recipient) {
|
||||
if (!recipient) return ''
|
||||
recipient = JSON.parse(recipient)
|
||||
return recipient.map(item => item.address).join(', ')
|
||||
}
|
||||
|
||||
function changeStar() {
|
||||
if (email.isStar) {
|
||||
email.isStar = 0;
|
||||
starCancel(email.emailId).then(() => {
|
||||
email.isStar = 0;
|
||||
emailStore.cancelStarEmailId = email.emailId
|
||||
if (email.value.isStar) {
|
||||
email.value.isStar = 0;
|
||||
starCancel(email.value.emailId).then(() => {
|
||||
email.value.isStar = 0;
|
||||
emailStore.cancelStarEmailId = email.value.emailId
|
||||
setTimeout(() => emailStore.cancelStarEmailId = 0)
|
||||
emailStore.starScroll?.deleteEmail([email.emailId])
|
||||
emailStore.starScroll?.deleteEmail([email.value.emailId])
|
||||
}).catch((e) => {
|
||||
console.error(e)
|
||||
email.isStar = 1;
|
||||
email.value.isStar = 1;
|
||||
})
|
||||
} else {
|
||||
email.isStar = 1;
|
||||
starAdd(email.emailId).then(() => {
|
||||
email.isStar = 1;
|
||||
emailStore.addStarEmailId = email.emailId
|
||||
email.value.isStar = 1;
|
||||
starAdd(email.value.emailId).then(() => {
|
||||
email.value.isStar = 1;
|
||||
emailStore.addStarEmailId = email.value.emailId
|
||||
setTimeout(() => emailStore.addStarEmailId = 0)
|
||||
emailStore.starScroll?.addItem(email)
|
||||
emailStore.starScroll?.addItem(email.value)
|
||||
}).catch((e) => {
|
||||
console.error(e)
|
||||
email.isStar = 0;
|
||||
email.value.isStar = 0;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -201,23 +208,23 @@ const handleDelete = () => {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
if (emailStore.contentData.delType === 'logic') {
|
||||
emailDelete(email.emailId).then(() => {
|
||||
emailDelete(email.value.emailId).then(() => {
|
||||
ElMessage({
|
||||
message: t('delSuccessMsg'),
|
||||
type: 'success',
|
||||
plain: true,
|
||||
})
|
||||
emailStore.deleteIds = [email.emailId]
|
||||
emailStore.deleteIds = [email.value.emailId]
|
||||
})
|
||||
} else {
|
||||
|
||||
allEmailDelete(email.emailId).then(() => {
|
||||
allEmailDelete(email.value.emailId).then(() => {
|
||||
ElMessage({
|
||||
message: t('delSuccessMsg'),
|
||||
type: 'success',
|
||||
plain: true,
|
||||
})
|
||||
emailStore.deleteIds = [email.emailId]
|
||||
emailStore.deleteIds = [email.value.emailId]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ function changeTimeSort() {
|
||||
}
|
||||
|
||||
function jumpContent(email) {
|
||||
emailStore.contentData.email = email
|
||||
emailStore.contentData.email = emailStore.toContentEmail(email)
|
||||
emailStore.contentData.delType = 'logic'
|
||||
emailStore.contentData.showUnread = true
|
||||
emailStore.contentData.showStar = true
|
||||
@@ -141,7 +141,9 @@ function cancelStar(email) {
|
||||
function getEmailList(emailId, size) {
|
||||
const accountId = accountStore.currentAccountId;
|
||||
const allReceive = accountStore.currentAccount.allReceive;
|
||||
return emailList(accountId, allReceive, emailId, params.timeSort, size, 0).then(data => {
|
||||
return emailStore.fetchList(full =>
|
||||
emailList(accountId, allReceive, emailId, params.timeSort, size, 0, full)
|
||||
).then(data => {
|
||||
data.latestEmail.reqAccountId = accountId;
|
||||
data.latestEmail.allReceive = allReceive;
|
||||
return data;
|
||||
|
||||
@@ -56,7 +56,7 @@ function changeTimeSort() {
|
||||
}
|
||||
|
||||
function jumpContent(email) {
|
||||
emailStore.contentData.email = email
|
||||
emailStore.contentData.email = emailStore.toContentEmail(email)
|
||||
emailStore.contentData.delType = 'logic'
|
||||
emailStore.contentData.showStar = true
|
||||
emailStore.contentData.showReply = true
|
||||
@@ -74,7 +74,9 @@ function cancelStar(email) {
|
||||
function getEmailList(emailId, size) {
|
||||
const accountId = accountStore.currentAccountId;
|
||||
const allReceive = accountStore.currentAccount.allReceive;
|
||||
return emailList(accountId, allReceive, emailId, params.timeSort, size, 1).then(data => {
|
||||
return emailStore.fetchList(full =>
|
||||
emailList(accountId, allReceive, emailId, params.timeSort, size, 1, full)
|
||||
).then(data => {
|
||||
data.latestEmail.reqAccountId = accountId;
|
||||
data.latestEmail.allReceive = allReceive;
|
||||
return data;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<emailScroll type="star" ref="scroll"
|
||||
:allow-star="false"
|
||||
:cancel-success="cancelStar"
|
||||
:getEmailList="starList"
|
||||
:getEmailList="getEmailList"
|
||||
:emailDelete="emailDelete"
|
||||
:star-add="starAdd"
|
||||
:star-cancel="starCancel"
|
||||
@@ -28,13 +28,17 @@ const scroll = ref({})
|
||||
const emailStore = useEmailStore();
|
||||
|
||||
function jumpContent(email) {
|
||||
emailStore.contentData.email = email
|
||||
emailStore.contentData.email = emailStore.toContentEmail(email)
|
||||
emailStore.contentData.delType = 'logic'
|
||||
emailStore.contentData.showStar = true
|
||||
emailStore.contentData.showReply = true
|
||||
router.push('/mail')
|
||||
}
|
||||
|
||||
function getEmailList(emailId, size) {
|
||||
return emailStore.fetchList(full => starList(emailId, size, full))
|
||||
}
|
||||
|
||||
function cancelStar(email) {
|
||||
emailStore.cancelStarEmailId = email.emailId
|
||||
scroll.value.deleteEmail([email.emailId])
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { emailConst } from '../const/entity-const';
|
||||
|
||||
const analysisDao = {
|
||||
async numberCount(c) {
|
||||
const { results } = await c.env.db.prepare(`
|
||||
@@ -26,7 +24,7 @@ const analysisDao = {
|
||||
SUM(CASE WHEN type = 0 AND is_del = 0 THEN 1 ELSE 0 END) AS normalReceiveTotal,
|
||||
SUM(CASE WHEN type = 1 AND is_del = 0 THEN 1 ELSE 0 END) AS normalSendTotal
|
||||
FROM
|
||||
email where status != ${emailConst.status.SAVING}
|
||||
email
|
||||
) e
|
||||
CROSS JOIN (
|
||||
SELECT
|
||||
|
||||
@@ -55,6 +55,20 @@ const dbInit = {
|
||||
} catch (e) {
|
||||
console.warn(`跳过字段:${e.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await c.env.db.batch([
|
||||
c.env.db.prepare(`CREATE INDEX IF NOT EXISTS idx_email_name_nocase ON email(name COLLATE NOCASE)`),
|
||||
c.env.db.prepare(`CREATE INDEX IF NOT EXISTS idx_email_subject_nocase ON email(subject COLLATE NOCASE)`),
|
||||
c.env.db.prepare(`CREATE INDEX IF NOT EXISTS idx_user_email_nocase ON user(email COLLATE NOCASE)`),
|
||||
c.env.db.prepare(`CREATE INDEX IF NOT EXISTS idx_email_to_email_nocase ON email(to_email COLLATE NOCASE)`),
|
||||
c.env.db.prepare(`CREATE INDEX IF NOT EXISTS idx_email_send_email_nocase ON email(send_email COLLATE NOCASE)`),
|
||||
c.env.db.prepare(`CREATE INDEX IF NOT EXISTS idx_email_noone_id ON email(email_id) WHERE status = 7`),
|
||||
c.env.db.prepare(`CREATE INDEX IF NOT EXISTS idx_email_type_id ON email(type, email_id)`)
|
||||
]);
|
||||
} catch (e) {
|
||||
console.warn(`跳过索引:${e.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
async v3_1DB(c) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { getTableColumns, sql } from 'drizzle-orm';
|
||||
import email from '../entity/email';
|
||||
|
||||
export const EMAIL_LIST_TEXT_LEN = 300;
|
||||
|
||||
/** 去掉换行/回车/制表符,并压缩连续空格、标签间空白 */
|
||||
function sqlStripWhitespace(column) {
|
||||
return sql`trim(replace(replace(replace(replace(replace(replace(
|
||||
coalesce(${column}, ''),
|
||||
char(13), ''),
|
||||
char(10), ''),
|
||||
char(9), ' '),
|
||||
' ', ' '),
|
||||
' ', ' '),
|
||||
'> <', '><'))`;
|
||||
}
|
||||
|
||||
/** 完整查询:全部字段 */
|
||||
export const emailListColumns = getTableColumns(email);
|
||||
|
||||
/** 摘要查询:列表 + 详情头部;有 text 则不读 content,没有才查 content(去空白),响应里不返回 content */
|
||||
export const emailBriefColumns = {
|
||||
emailId: email.emailId,
|
||||
sendEmail: email.sendEmail,
|
||||
name: email.name,
|
||||
subject: email.subject,
|
||||
code: email.code,
|
||||
recipient: email.recipient,
|
||||
toEmail: email.toEmail,
|
||||
type: email.type,
|
||||
status: email.status,
|
||||
message: email.message,
|
||||
unread: email.unread,
|
||||
createTime: email.createTime,
|
||||
isDel: email.isDel,
|
||||
content: sql`CASE WHEN trim(coalesce(${email.text}, '')) != '' THEN NULL ELSE ${sqlStripWhitespace(email.content)} END`.as('content'),
|
||||
text: sql`substr(coalesce(${email.text}, ''), 1, ${EMAIL_LIST_TEXT_LEN})`.as('text'),
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import orm from '../entity/orm';
|
||||
import email from '../entity/email';
|
||||
import { emailListColumns, emailBriefColumns, EMAIL_LIST_TEXT_LEN } from '../lib/email-list-columns';
|
||||
import { attConst, emailConst, isDel, settingConst } from '../const/entity-const';
|
||||
import { and, desc, eq, gt, inArray, lt, count, asc, sql, ne, or, like, lte, gte } from 'drizzle-orm';
|
||||
import { star } from '../entity/star';
|
||||
@@ -27,13 +28,14 @@ const emailService = {
|
||||
|
||||
async list(c, params, userId) {
|
||||
|
||||
let { emailId, type, accountId, size, timeSort, allReceive } = params;
|
||||
let { emailId, type, accountId, size, timeSort, allReceive, full } = params;
|
||||
|
||||
size = Number(size);
|
||||
emailId = Number(emailId) || 0;
|
||||
timeSort = Number(timeSort);
|
||||
accountId = Number(accountId);
|
||||
allReceive = Number(allReceive);
|
||||
full = Number(full) === 1;
|
||||
|
||||
if (size > 50) {
|
||||
size = 50;
|
||||
@@ -46,10 +48,11 @@ const emailService = {
|
||||
|
||||
const filters = this.emailListFilters({ userId, accountId, type, allReceive, emailId, timeSort });
|
||||
const countFilters = this.emailListFilters({ userId, accountId, type, allReceive, withCursor: false });
|
||||
const columns = full ? emailListColumns : emailBriefColumns;
|
||||
|
||||
const query = orm(c)
|
||||
.select({
|
||||
...email,
|
||||
...columns,
|
||||
starId: star.starId
|
||||
})
|
||||
.from(email)
|
||||
@@ -82,7 +85,11 @@ const emailService = {
|
||||
.where(and(...countFilters))
|
||||
.get();
|
||||
|
||||
const latestEmailQuery = orm(c).select().from(email).where(
|
||||
const latestEmailQuery = orm(c).select({
|
||||
emailId: email.emailId,
|
||||
accountId: email.accountId,
|
||||
userId: email.userId,
|
||||
}).from(email).where(
|
||||
and(
|
||||
eq(email.userId, userId),
|
||||
eq(email.type, type),
|
||||
@@ -98,8 +105,11 @@ const emailService = {
|
||||
isStar: item.starId != null ? 1 : 0
|
||||
}));
|
||||
|
||||
|
||||
await this.emailAddAtt(c, list);
|
||||
if (full) {
|
||||
await this.emailAddAtt(c, list);
|
||||
} else {
|
||||
this.applyListText(list);
|
||||
}
|
||||
|
||||
if (!latestEmail) {
|
||||
latestEmail = {
|
||||
@@ -112,6 +122,19 @@ const emailService = {
|
||||
return { list, total: totalRow.total, latestEmail };
|
||||
},
|
||||
|
||||
toListText(item) {
|
||||
const raw = emailUtils.formatText(item.text) || emailUtils.htmlToText(item.content);
|
||||
return raw.replace(/\s+/g, ' ').trim().slice(0, EMAIL_LIST_TEXT_LEN);
|
||||
},
|
||||
|
||||
applyListText(list) {
|
||||
for (const item of list) {
|
||||
item.text = this.toListText(item);
|
||||
delete item.content;
|
||||
}
|
||||
return list;
|
||||
},
|
||||
|
||||
emailListFilters({ userId, accountId, type, allReceive, emailId, timeSort, withCursor = true }) {
|
||||
const conditions = [
|
||||
eq(email.userId, userId),
|
||||
@@ -148,28 +171,26 @@ const emailService = {
|
||||
}
|
||||
|
||||
if (userEmail) {
|
||||
conditions.push(sql`${user.email} COLLATE NOCASE LIKE ${'%' + userEmail + '%'}`);
|
||||
conditions.push(sql`${user.email} COLLATE NOCASE LIKE ${userEmail + '%'}`);
|
||||
}
|
||||
|
||||
if (accountEmail) {
|
||||
conditions.push(
|
||||
or(
|
||||
sql`${email.toEmail} COLLATE NOCASE LIKE ${'%' + accountEmail + '%'}`,
|
||||
sql`${email.sendEmail} COLLATE NOCASE LIKE ${'%' + accountEmail + '%'}`,
|
||||
sql`${email.toEmail} COLLATE NOCASE LIKE ${accountEmail + '%'}`,
|
||||
sql`${email.sendEmail} COLLATE NOCASE LIKE ${accountEmail + '%'}`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (name) {
|
||||
conditions.push(sql`${email.name} COLLATE NOCASE LIKE ${'%' + name + '%'}`);
|
||||
conditions.push(sql`${email.name} COLLATE NOCASE LIKE ${name + '%'}`);
|
||||
}
|
||||
|
||||
if (subject) {
|
||||
conditions.push(sql`${email.subject} COLLATE NOCASE LIKE ${'%' + subject + '%'}`);
|
||||
conditions.push(sql`${email.subject} COLLATE NOCASE LIKE ${subject + '%'}`);
|
||||
}
|
||||
|
||||
conditions.push(ne(email.status, emailConst.status.SAVING));
|
||||
|
||||
if (withCursor && emailId) {
|
||||
conditions.push(timeSort ? gt(email.emailId, emailId) : lt(email.emailId, emailId));
|
||||
}
|
||||
@@ -792,7 +813,7 @@ const emailService = {
|
||||
allReceive = accountRow.allReceive;
|
||||
}
|
||||
|
||||
let list = await orm(c).select({...email}).from(email)
|
||||
let list = await orm(c).select({ ...emailBriefColumns }).from(email)
|
||||
.innerJoin(
|
||||
account,
|
||||
eq(account.accountId, email.accountId)
|
||||
@@ -809,9 +830,7 @@ const emailService = {
|
||||
.orderBy(desc(email.emailId))
|
||||
.limit(20);
|
||||
|
||||
await this.emailAddAtt(c, list);
|
||||
|
||||
return list;
|
||||
return this.applyListText(list);
|
||||
},
|
||||
|
||||
async physicsDelete(c, params) {
|
||||
@@ -854,12 +873,13 @@ const emailService = {
|
||||
|
||||
async allList(c, params) {
|
||||
|
||||
let { emailId, size, name, subject, accountEmail, userEmail, type, timeSort } = params;
|
||||
let { emailId, size, name, subject, accountEmail, userEmail, type, timeSort, full } = params;
|
||||
|
||||
size = Number(size);
|
||||
|
||||
emailId = Number(emailId) || 0;
|
||||
timeSort = Number(timeSort);
|
||||
full = Number(full) === 1;
|
||||
|
||||
if (size > 50) {
|
||||
size = 50;
|
||||
@@ -867,16 +887,22 @@ const emailService = {
|
||||
|
||||
const filters = this.allEmailListFilters({ emailId, name, subject, accountEmail, userEmail, type, timeSort });
|
||||
const countFilters = this.allEmailListFilters({ emailId, name, subject, accountEmail, userEmail, type, timeSort, withCursor: false });
|
||||
const columns = full ? emailListColumns : emailBriefColumns;
|
||||
|
||||
const query = orm(c).select({ ...email, userEmail: user.email })
|
||||
const query = orm(c).select({ ...columns, userEmail: user.email })
|
||||
.from(email)
|
||||
.leftJoin(user, eq(email.userId, user.userId))
|
||||
.where(and(...filters));
|
||||
|
||||
const queryCount = orm(c).select({ total: count() })
|
||||
.from(email)
|
||||
.leftJoin(user, eq(email.userId, user.userId))
|
||||
.where(and(...countFilters));
|
||||
// count 不搜用户时无需 join user
|
||||
const queryCount = userEmail
|
||||
? orm(c).select({ total: count() })
|
||||
.from(email)
|
||||
.leftJoin(user, eq(email.userId, user.userId))
|
||||
.where(and(...countFilters))
|
||||
: orm(c).select({ total: count() })
|
||||
.from(email)
|
||||
.where(and(...countFilters));
|
||||
|
||||
if (timeSort) {
|
||||
query.orderBy(asc(email.emailId));
|
||||
@@ -886,16 +912,21 @@ const emailService = {
|
||||
|
||||
const listQuery = query.limit(size).all();
|
||||
const totalQuery = queryCount.get();
|
||||
const latestEmailQuery = orm(c).select().from(email)
|
||||
.where(and(
|
||||
eq(email.type, emailConst.type.RECEIVE),
|
||||
ne(email.status, emailConst.status.SAVING)
|
||||
))
|
||||
const latestEmailQuery = orm(c).select({
|
||||
emailId: email.emailId,
|
||||
accountId: email.accountId,
|
||||
userId: email.userId,
|
||||
}).from(email)
|
||||
.where(eq(email.type, emailConst.type.RECEIVE))
|
||||
.orderBy(desc(email.emailId)).limit(1).get();
|
||||
|
||||
let [list, totalRow, latestEmail] = await Promise.all([listQuery, totalQuery, latestEmailQuery]);
|
||||
|
||||
await this.emailAddAtt(c, list);
|
||||
if (full) {
|
||||
await this.emailAddAtt(c, list);
|
||||
} else {
|
||||
this.applyListText(list);
|
||||
}
|
||||
|
||||
if (!latestEmail) {
|
||||
latestEmail = {
|
||||
@@ -912,20 +943,17 @@ const emailService = {
|
||||
|
||||
const { emailId } = params;
|
||||
|
||||
let list = await orm(c).select({...email, userEmail: user.email}).from(email)
|
||||
let list = await orm(c).select({ ...emailBriefColumns, userEmail: user.email }).from(email)
|
||||
.leftJoin(user, eq(email.userId, user.userId))
|
||||
.where(
|
||||
and(
|
||||
gt(email.emailId, emailId),
|
||||
eq(email.type, emailConst.type.RECEIVE),
|
||||
ne(email.status, emailConst.status.SAVING)
|
||||
eq(email.type, emailConst.type.RECEIVE)
|
||||
))
|
||||
.orderBy(desc(email.emailId))
|
||||
.limit(20);
|
||||
|
||||
await this.emailAddAtt(c, list);
|
||||
|
||||
return list;
|
||||
return this.applyListText(list);
|
||||
},
|
||||
|
||||
async emailAddAtt(c, list) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import emailService from './email-service';
|
||||
import BizError from '../error/biz-error';
|
||||
import { and, desc, eq, lt, sql, inArray } from 'drizzle-orm';
|
||||
import email from '../entity/email';
|
||||
import { emailListColumns, emailBriefColumns } from '../lib/email-list-columns';
|
||||
import { isDel } from '../const/entity-const';
|
||||
import attService from "./att-service";
|
||||
import { t } from '../i18n/i18n'
|
||||
@@ -41,14 +42,16 @@ const starService = {
|
||||
},
|
||||
|
||||
async list(c, params, userId) {
|
||||
let { emailId, size } = params;
|
||||
let { emailId, size, full } = params;
|
||||
emailId = Number(emailId) || 0;
|
||||
size = Number(size);
|
||||
full = Number(full) === 1;
|
||||
const columns = full ? emailListColumns : emailBriefColumns;
|
||||
|
||||
const list = await orm(c).select({
|
||||
isStar: sql`1`.as('isStar'),
|
||||
starId: star.starId
|
||||
, ...email
|
||||
starId: star.starId,
|
||||
...columns
|
||||
}).from(star)
|
||||
.leftJoin(email, eq(email.emailId, star.emailId))
|
||||
.where(
|
||||
@@ -60,14 +63,18 @@ const starService = {
|
||||
.limit(size)
|
||||
.all();
|
||||
|
||||
const emailIds = list.map(item => item.emailId);
|
||||
|
||||
const attsList = await attService.selectByEmailIds(c, emailIds);
|
||||
|
||||
list.forEach(emailRow => {
|
||||
const atts = attsList.filter(attsRow => attsRow.emailId === emailRow.emailId);
|
||||
emailRow.attList = atts;
|
||||
});
|
||||
if (full) {
|
||||
const emailIds = list.map(item => item.emailId);
|
||||
const attsList = await attService.selectByEmailIds(c, emailIds);
|
||||
list.forEach(emailRow => {
|
||||
emailRow.attList = attsList.filter(attsRow => attsRow.emailId === emailRow.emailId);
|
||||
});
|
||||
} else {
|
||||
list.forEach(emailRow => {
|
||||
emailRow.text = emailService.toListText(emailRow);
|
||||
delete emailRow.content;
|
||||
});
|
||||
}
|
||||
|
||||
return { list };
|
||||
},
|
||||
|
||||
@@ -350,6 +350,10 @@ const userService = {
|
||||
},
|
||||
|
||||
async resetDaySendCount(c) {
|
||||
// 仅 UTC 0 点执行,便于配合每小时 cron
|
||||
if (new Date().getUTCHours() !== 0) {
|
||||
return;
|
||||
}
|
||||
const roleList = await roleService.selectByIdsAndSendType(c, 'email:send', roleConst.sendType.DAY);
|
||||
const roleIds = roleList.map(action => action.roleId);
|
||||
await orm(c).update(user).set({ sendCount: 0 }).where(inArray(user.type, roleIds)).run();
|
||||
|
||||
@@ -32,7 +32,7 @@ not_found_handling = "single-page-application"
|
||||
run_worker_first = true
|
||||
|
||||
[triggers]
|
||||
crons = ["0 16 * * *"]
|
||||
crons = ["0 * * * *"] #每小时执行;resetDaySendCount 仅在 UTC 0 点生效
|
||||
|
||||
|
||||
[vars]
|
||||
|
||||
@@ -38,7 +38,7 @@ run_worker_first = true
|
||||
[vars]
|
||||
ai_model = ""
|
||||
analysis_cache = false
|
||||
orm_log = false
|
||||
orm_log = true
|
||||
domain = ["example.com", "example2.com", "example3.com", "example4.com"]
|
||||
admin = "[email protected]"
|
||||
jwt_secret = "b7f29a1d-18e2-4d3b-941f-f6b2c97c02fd"
|
||||
|
||||
@@ -30,7 +30,7 @@ not_found_handling = "single-page-application"
|
||||
run_worker_first = true
|
||||
|
||||
[triggers]
|
||||
crons = ["0 16 * * *"] #每天晚上12点执行每日任务,刷新分析缓存
|
||||
crons = ["0 * * * *"] #每小时执行;resetDaySendCount 仅在 UTC 0 点生效
|
||||
|
||||
|
||||
[vars]
|
||||
|
||||
@@ -32,7 +32,7 @@ not_found_handling = "single-page-application"
|
||||
run_worker_first = true
|
||||
|
||||
[triggers]
|
||||
crons = ["0 16 * * *"] #每天晚上12点执行每日任务,刷新分析缓存
|
||||
crons = ["0 * * * *"] #每小时执行;resetDaySendCount 仅在 UTC 0 点生效
|
||||
|
||||
|
||||
[vars]
|
||||
|
||||
Reference in New Issue
Block a user