feat: push Douyin events through shared WebSocket and SSE
douyin-release-gate / verify (push) Failing after 18m0s

This commit is contained in:
2026-10-07 23:45:32 +08:00
parent 297b813b90
commit 360bf96bab
30 changed files with 1625 additions and 140 deletions
+31 -4
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { Alert, Avatar, Button, Card, Flex, Select, Table, Tag, Tooltip, Typography } from 'antd';
import { ReloadOutlined, VideoCameraOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { creatorGet } from '@/services/api';
import { creatorGet, creatorSubscribe } from '@/services/api';
import { usePageState } from '@/hooks/usePageState';
import { conflictMessage, dateTime } from '@/utils/helpers';
import { tablePagination, useTableSticky } from '@/utils/table';
@@ -75,17 +75,25 @@ export default function EventAggregationPage() {
const [pending, setPending] = useState(true);
const [error, setError] = useState<unknown>(null);
const [revision, setRevision] = useState(0);
const [streamStatus, setStreamStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting');
const sticky = useTableSticky();
useEffect(() => {
let active = true;
let timer: ReturnType<typeof setTimeout>;
let busy = false;
let queued = false;
const controller = new AbortController();
setStreamStatus('connecting');
const query = eventQuery(filters, pagination.page, pagination.pageSize);
setPending(true);
setError(null);
setEvents([]);
setTotal(0);
async function load() {
if (!active) return;
if (busy) { queued = true; return; }
busy = true;
try {
const [page, profiles] = await Promise.all([
creatorGet(`/creator/events?${query}`), creatorGet('/creator/accounts'),
@@ -100,14 +108,32 @@ export default function EventAggregationPage() {
console.error('读取账号事件失败', { query, error: cause });
if (active) setError(cause);
} finally {
busy = false;
if (active) {
setPending(false);
timer = setTimeout(load, 5000);
if (queued) { queued = false; void load(); }
}
}
}
async function connect() {
try {
await creatorSubscribe('/creator/updates', () => { if (active) void load(); }, controller.signal,
(status) => {
if (!active) return;
setStreamStatus(status);
if (status === 'connected') void load();
});
} catch (cause) {
if (active) console.error('事件实时更新连接失败', { error: cause });
}
if (active) {
setStreamStatus('disconnected');
timer = setTimeout(connect, 2000);
}
}
void load();
return () => { active = false; clearTimeout(timer); };
void connect();
return () => { active = false; controller.abort(); clearTimeout(timer); };
}, [filters.accountId, filters.eventType, filters.hours, pagination.page, pagination.pageSize, revision]);
function updateFilters(next: Partial<EventFilters>) {
@@ -118,7 +144,7 @@ export default function EventAggregationPage() {
return (
<Card variant="outlined">
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
<Typography.Text type="secondary">共 {total} 条事件 · 每 5 秒刷新</Typography.Text>
<Typography.Text type="secondary">共 {total} 条事件 · {streamStatus === 'connected' ? '实时更新' : streamStatus === 'connecting' ? '正在连接实时更新' : '实时更新已断开'}</Typography.Text>
<Button icon={<ReloadOutlined />} disabled={pending} onClick={() => setRevision((value) => value + 1)}>刷新</Button>
</Flex>
<Flex wrap gap="middle" align="center" style={{ marginBottom: 16 }}>
@@ -133,6 +159,7 @@ export default function EventAggregationPage() {
{ value: 24, label: '最近 1 天' }, { value: 72, label: '最近 3 天' }, { value: 168, label: '最近 7 天' }]} />
</Flex>
<Typography.Paragraph type="secondary">仅展示已开启监听账号的事件;停止监听的账号不在此列表展示,历史记录仍保留。时间范围按接收时间计算,仅记录事件,不执行自动互动。私信在独立收件箱展示。</Typography.Paragraph>
{streamStatus === 'disconnected' ? <Alert type="warning" showIcon title="实时更新连接已断开" description="正在重新连接;当前列表保留,也可手动刷新。" style={{ marginBottom: 16 }} /> : null}
{error ? <Alert type="error" showIcon title="事件读取失败" description={conflictMessage(error, '请检查服务状态后重试。')} style={{ marginBottom: 16 }} /> : null}
<Table<AccountEvent> rowKey="id" columns={columns} dataSource={events} loading={pending} sticky={sticky}
tableLayout="fixed"