1 Commits
Author SHA1 Message Date
gr 5db9674490 更新 2026-07-31 16:37:23 +08:00
3 changed files with 54 additions and 11 deletions
+30 -6
View File
@@ -2567,14 +2567,25 @@ async def get_logs(
if account_id is not None: if account_id is not None:
await get_owned_account(db, user, account_id) await get_owned_account(db, user, account_id)
limit = max(1, min(int(limit or 50), 500)) limit = max(1, min(int(limit or 50), 500))
stmt = ( # Fetch only primary keys while MySQL performs the cross-account sort.
# Selecting the full ORM row here includes MEDIUMTEXT/TEXT columns, which
# makes MySQL 5.7 materialize a huge on-disk temporary table for non-admin
# users. Under polling load that grew ibtmp1 by tens of gigabytes.
id_stmt = (
logs_for_user(user, account_id) logs_for_user(user, account_id)
.with_only_columns(MessageLog.id)
.order_by(MessageLog.created_at.desc()) .order_by(MessageLog.created_at.desc())
.offset(max(0, int(offset or 0))) .offset(max(0, int(offset or 0)))
.limit(limit) .limit(limit)
) )
result = await db.execute(stmt) ordered_ids = list((await db.execute(id_stmt)).scalars().all())
return result.scalars().all() if not ordered_ids:
return []
rows = (
await db.execute(select(MessageLog).where(MessageLog.id.in_(ordered_ids)))
).scalars().all()
rows_by_id = {row.id: row for row in rows}
return [rows_by_id[row_id] for row_id in ordered_ids if row_id in rows_by_id]
@app.get("/api/received-messages", response_model=List[ReceivedMessageLogResponse]) @app.get("/api/received-messages", response_model=List[ReceivedMessageLogResponse])
@@ -2588,13 +2599,26 @@ async def get_received_messages(
if account_id is not None: if account_id is not None:
await get_owned_account(db, user, account_id) await get_owned_account(db, user, account_id)
limit = max(1, min(int(limit or 100), 500)) limit = max(1, min(int(limit or 100), 500))
stmt = ( # Keep the global sort narrow for the same reason as /api/logs. raw_content
# can be large and must only be loaded after LIMIT has selected the IDs.
id_stmt = (
received_logs_for_user(user, account_id) received_logs_for_user(user, account_id)
.with_only_columns(ReceivedMessageLog.id)
.order_by(ReceivedMessageLog.created_at.desc()) .order_by(ReceivedMessageLog.created_at.desc())
.limit(limit) .limit(limit)
) )
result = await db.execute(stmt) ordered_ids = list((await db.execute(id_stmt)).scalars().all())
return result.scalars().all() if not ordered_ids:
return []
rows = (
await db.execute(
select(ReceivedMessageLog).where(
ReceivedMessageLog.id.in_(ordered_ids)
)
)
).scalars().all()
rows_by_id = {row.id: row for row in rows}
return [rows_by_id[row_id] for row_id in ordered_ids if row_id in rows_by_id]
@app.get("/api/system-logs", response_model=List[SystemLogResponse]) @app.get("/api/system-logs", response_model=List[SystemLogResponse])
+6
View File
@@ -126,6 +126,12 @@ def migrate_accounts_table(conn) -> None:
"douyin_uid", "douyin_uid",
{"default": "ALTER TABLE accounts ADD COLUMN douyin_uid VARCHAR(64)"}, {"default": "ALTER TABLE accounts ADD COLUMN douyin_uid VARCHAR(64)"},
) )
add_index_if_missing(
conn,
"accounts",
"ix_accounts_owner_id",
("owner_id",),
)
def migrate_account_videos_table(conn) -> None: def migrate_account_videos_table(conn) -> None:
+18 -5
View File
@@ -39,9 +39,23 @@ const LOGS_PAGE_SIZE = 20
const hasMoreLogs = ref(true) const hasMoreLogs = ref(true)
const loadingMore = ref(false) const loadingMore = ref(false)
let pollTimer = null let pollTimer = null
let pollStopped = false
// 上次日志数据的签名;轮询时数据没变化就跳过赋值,避免无谓的重算与重渲染 // 上次日志数据的签名;轮询时数据没变化就跳过赋值,避免无谓的重算与重渲染
let lastLogsSignature = '' let lastLogsSignature = ''
// Use a self-scheduling timeout instead of setInterval. A slow request must
// finish before the next poll is scheduled, otherwise overlapping requests can
// exhaust the database pool and amplify one slow query into dozens.
const scheduleLogPoll = () => {
if (pollStopped) return
pollTimer = setTimeout(async () => {
if (!document.hidden) {
await fetchLogs(true)
}
scheduleLogPoll()
}, POLL_INTERVAL_MS)
}
const dedupeMessages = (messages) => { const dedupeMessages = (messages) => {
const map = new Map() const map = new Map()
for (const item of messages) { for (const item of messages) {
@@ -593,10 +607,8 @@ onMounted(async () => {
} }
await fetchAccounts() await fetchAccounts()
await fetchLogs() await fetchLogs()
pollTimer = setInterval(() => { pollStopped = false
if (document.hidden) return scheduleLogPoll()
fetchLogs(true)
}, POLL_INTERVAL_MS)
}) })
watch( watch(
@@ -613,8 +625,9 @@ watch(
) )
onUnmounted(() => { onUnmounted(() => {
pollStopped = true
if (pollTimer) { if (pollTimer) {
clearInterval(pollTimer) clearTimeout(pollTimer)
pollTimer = null pollTimer = null
} }
}) })