Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5db9674490 |
+30
-6
@@ -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])
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user