import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AlertTriangle, BarChart3, CheckCircle2, Eye, RefreshCw, Search, TimerReset } from 'lucide-react'; import { adminApi, type DownstreamDeliveryDashboard, type DownstreamDeliveryRecord, type DownstreamRequeuePreview, type DownstreamRequeueTask, type DownstreamRequeueTaskItem, type EnterpriseApplication, type TenantOption } from '@/api/adminApi'; import { Breadcrumb, Button, DateRangeInput, Input, Modal, Pagination, Select, Tag, Textarea, type DateRangeValue } from '@/components/ui'; import { formatDateTime } from '@/utils/dateTime'; const statusTone: Record = { pending: 'warning', manual_requeueing: 'info', awaiting_ack: 'info', delivered: 'success', failed: 'danger', unconfirmed: 'warning', rejected: 'danger', }; const statusLabel: Record = { manual_requeueing: '人工重投处理中', awaiting_ack: '等待客户端确认', delivered: '客户端已确认', failed: '投递失败', unconfirmed: '客户端未确认', rejected: '客户端拒绝', }; function deliveryStatusLabel(record: DownstreamDeliveryRecord) { if (record.status !== 'pending') return statusLabel[record.status] ?? record.status; if (record.manualRetryCount > 0) return '人工重投排队中'; if (record.retryCount > 0) return '等待自动重试'; return '待首次投递'; } const deliveryTypeLabel: Record = { receipt: '状态回执', uplink: '上行短信', }; const attemptStatusLabel: Record = { awaiting_ack: '等待客户端确认', acknowledged: '客户端已确认', rejected: '客户端拒绝', failed: '投递失败', }; const requeueTaskStatusLabel: Record = { queued: '排队中', running: '执行中', paused: '已暂停', completed: '已完成', partial_completed: '部分完成', terminated: '已终止', }; const requeueItemStatusLabel: Record = { queued: '排队中', processing: '处理中', waiting_connection: '等待连接', waiting_external_ack: '等待其他链路确认', waiting_ack: '等待客户确认', success: '成功', failed: '失败', skipped: '跳过', unprocessed: '未处理', }; function requeueTone(status: string) { if (status === 'completed' || status === 'success') return 'success' as const; if (status === 'partial_completed' || status === 'failed') return 'danger' as const; if (status === 'paused' || status === 'skipped' || status === 'unprocessed') return 'warning' as const; return 'info' as const; } type RequeueTarget = | { kind: 'single'; record: DownstreamDeliveryRecord } | { kind: 'batch'; ids: string[] }; type RequeueResult = { status: 'success' | 'partial' | 'failed'; title: string; message: string; failures?: string[]; }; function formatLocalDate(value: Date) { return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`; } function recentSevenDays(): DateRangeValue { const end = new Date(); const start = new Date(end); start.setDate(end.getDate() - 6); return { start: formatLocalDate(start), end: formatLocalDate(end) }; } function attemptStatusTone(status: string) { if (status === 'acknowledged') return 'success' as const; if (status === 'rejected' || status === 'failed') return 'danger' as const; if (status === 'awaiting_ack') return 'info' as const; return 'neutral' as const; } function DeliveryDetailModal({ record, onClose }: { record: DownstreamDeliveryRecord; onClose: () => void }) { const payloadText = useMemo(() => JSON.stringify(record.payload ?? {}, null, 2), [record.payload]); return (

下游投递详情

{record.id}

} footer={} >
企业{record.tenant?.name ?? record.tenantId}
应用{record.application?.name ?? record.applicationId}
投递类型{deliveryTypeLabel[record.deliveryType] ?? record.deliveryType}
当前状态{deliveryStatusLabel(record)}
消息 ID{record.messageId ?? '-'}
自动重试次数{record.retryCount}
人工重投次数{record.manualRetryCount}
最近人工重投{record.lastRetriedAt ? formatDateTime(record.lastRetriedAt) : '-'}
下次重试{record.nextRetryAt ?? '-'}
自动重试{record.retryEnabled ? '开启' : '关闭'}
写出时间{record.sentAt ?? '-'}
确认时间{record.acknowledgedAt ?? '-'}
ACK Result{record.ackResult ?? '-'}
ACK Sequence_Id{record.ackSequenceId ?? '-'}
ACK Msg_Id{record.ackMessageId ?? '-'}
连接 ID{record.connectionId ?? '-'}
最后错误{record.lastError ?? '-'}

逐次投递记录

{(record.attempts ?? []).map((attempt) => (
{attempt.attemptNo}
第 {attempt.attemptNo} 次投递 {attemptStatusLabel[attempt.status] ?? attempt.status}
发送时间
{attempt.sentAt ? formatDateTime(attempt.sentAt) : '-'}
ACK 时间
{attempt.acknowledgedAt ? formatDateTime(attempt.acknowledgedAt) : '-'}
ACK 截止
{attempt.ackDeadlineAt ? formatDateTime(attempt.ackDeadlineAt) : '-'}
连接 ID
{attempt.connectionId ?? '-'}
Sequence_Id
{attempt.sequenceId ?? '-'}
Msg_Id
{attempt.messageId ?? '-'}
ACK Result:{attempt.ackResult ?? '-'} {attempt.errorMessage ?? attempt.failureType ?? '本次投递未记录异常'}
))} {(record.attempts ?? []).length === 0 ?

暂无逐次投递记录

: null}

Payload

{payloadText}
); } export function AdminDownstreamDeliveriesPage() { const [records, setRecords] = useState([]); const [dashboard, setDashboard] = useState(null); const [applications, setApplications] = useState([]); const [tenants, setTenants] = useState([]); const [keyword, setKeyword] = useState(''); const [status, setStatus] = useState('all'); const [deliveryType, setDeliveryType] = useState('all'); const [applicationId, setApplicationId] = useState('all'); const [tenantId, setTenantId] = useState('all'); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [detail, setDetail] = useState(null); const [selectedIds, setSelectedIds] = useState([]); const [dateRange, setDateRange] = useState(recentSevenDays); const [requeueTarget, setRequeueTarget] = useState(null); const [requeueBusy, setRequeueBusy] = useState(false); const [requeueResult, setRequeueResult] = useState(null); const requeueInFlightRef = useRef(false); const [taskPreview, setTaskPreview] = useState(null); const [taskPreviewBusy, setTaskPreviewBusy] = useState(false); const [taskReason, setTaskReason] = useState(''); const [taskRate, setTaskRate] = useState(10); const [taskCreateBusy, setTaskCreateBusy] = useState(false); const [requeueTasks, setRequeueTasks] = useState([]); const [requeueTaskStatus, setRequeueTaskStatus] = useState('all'); const [requeueTaskPage, setRequeueTaskPage] = useState(1); const [requeueTaskTotal, setRequeueTaskTotal] = useState(0); const [selectedTask, setSelectedTask] = useState(null); const [taskItems, setTaskItems] = useState([]); const [taskItemStatus, setTaskItemStatus] = useState('all'); const [taskItemKeyword, setTaskItemKeyword] = useState(''); const [taskItemAppliedKeyword, setTaskItemAppliedKeyword] = useState(''); const [taskItemPage, setTaskItemPage] = useState(1); const [taskItemTotal, setTaskItemTotal] = useState(0); const currentTaskFilter = useCallback(() => ({ keyword: keyword || undefined, status, deliveryType, tenantId, applicationId, createdAtFrom: dateRange.start, createdAtTo: dateRange.end, }), [applicationId, dateRange.end, dateRange.start, deliveryType, keyword, status, tenantId]); const loadRequeueTasks = useCallback(() => { adminApi.listDownstreamRequeueTasks({ status: requeueTaskStatus, page: requeueTaskPage, pageSize: 10 }) .then((response) => { setRequeueTasks(response.items); setRequeueTaskTotal(response.total); }) .catch((failure: Error) => setError(failure.message || '后台重投任务加载失败')); }, [requeueTaskPage, requeueTaskStatus]); const loadData = useCallback(() => { setLoading(true); Promise.all([ adminApi.getDownstreamDeliveryDashboard({ applicationId, tenantId, deliveryType, createdAtFrom: dateRange.start, createdAtTo: dateRange.end, }), adminApi.listDownstreamDeliveries({ keyword, status, deliveryType, applicationId, tenantId, page, pageSize, createdAtFrom: dateRange.start, createdAtTo: dateRange.end, }), adminApi.listEnterpriseApplications(), adminApi.listTenants(), ]) .then(([dashboardResponse, response, apps, tenantOptions]) => { setDashboard(dashboardResponse); setRecords(response.items); setTotal(response.total); setApplications(apps); setTenants(tenantOptions); setSelectedIds((current) => current.filter((id) => response.items.some((item) => item.id === id))); setError(''); }) .catch((failure: Error) => setError(failure.message || '下游投递记录加载失败')) .finally(() => setLoading(false)); }, [applicationId, dateRange.end, dateRange.start, deliveryType, keyword, page, pageSize, status, tenantId]); useEffect(() => { loadData(); }, [loadData]); useEffect(() => { loadRequeueTasks(); const timer = window.setInterval(loadRequeueTasks, 3000); return () => window.clearInterval(timer); }, [loadRequeueTasks]); const openTaskPreview = async () => { setTaskPreviewBusy(true); setError(''); try { setTaskPreview(await adminApi.previewDownstreamRequeueTask(currentTaskFilter())); setTaskReason(''); } catch (failure) { setError(failure instanceof Error ? failure.message : '后台重投范围预检失败'); } finally { setTaskPreviewBusy(false); } }; const createRequeueTask = async () => { if (!taskPreview || taskReason.trim().length < 5) return; setTaskCreateBusy(true); try { await adminApi.createDownstreamRequeueTask({ previewToken: taskPreview.previewToken, reason: taskReason.trim(), ratePerSecond: taskRate, consecutiveFailureLimit: 10 }); setTaskPreview(null); loadRequeueTasks(); } catch (failure) { setError(failure instanceof Error ? failure.message : '后台重投任务创建失败'); } finally { setTaskCreateBusy(false); } }; const filteredApplications = useMemo( () => applications.filter((item) => tenantId === 'all' || item.tenantId === tenantId), [applications, tenantId], ); const openTaskDetail = async (id: string) => { try { const task = await adminApi.getDownstreamRequeueTask(id); setSelectedTask(task); setTaskItemStatus('all'); setTaskItemKeyword(''); setTaskItemAppliedKeyword(''); setTaskItemPage(1); } catch (failure) { setError(failure instanceof Error ? failure.message : '任务详情加载失败'); } }; const loadTaskItems = useCallback(() => { if (!selectedTask) return; adminApi.listDownstreamRequeueTaskItems(selectedTask.id, { status: taskItemStatus, keyword: taskItemAppliedKeyword || undefined, page: taskItemPage, pageSize: 20 }) .then((response) => { setTaskItems(response.items); setTaskItemTotal(response.total); }) .catch((failure: Error) => setError(failure.message || '任务明细加载失败')); }, [selectedTask, taskItemAppliedKeyword, taskItemPage, taskItemStatus]); useEffect(() => { loadTaskItems(); }, [loadTaskItems]); const replayableStatuses = useMemo(() => new Set(['pending', 'delivered', 'failed', 'unconfirmed', 'rejected']), []); const selectableIds = useMemo( () => records.filter((item) => replayableStatuses.has(item.status)).map((item) => item.id), [records, replayableStatuses], ); const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selectedIds.includes(id)); const summary = dashboard?.summary; const typeBreakdown = dashboard?.typeBreakdown ?? []; const retryBuckets = dashboard?.retryBuckets ?? []; const topApplications = dashboard?.topApplications ?? []; const totalPages = Math.max(1, Math.ceil(total / pageSize)); const openSingleRequeue = (record: DownstreamDeliveryRecord) => { setRequeueResult(null); setRequeueTarget({ kind: 'single', record }); }; const openBatchRequeue = () => { if (selectedIds.length === 0) return; setRequeueResult(null); setRequeueTarget({ kind: 'batch', ids: [...selectedIds] }); }; const closeRequeueDialog = () => { if (requeueBusy) return; setRequeueTarget(null); setRequeueResult(null); }; const confirmRequeue = async () => { if (!requeueTarget || requeueInFlightRef.current) return; requeueInFlightRef.current = true; setRequeueBusy(true); setRequeueResult(null); setError(''); try { if (requeueTarget.kind === 'single') { const updated = await adminApi.requeueDownstreamDelivery(requeueTarget.record.id); setRequeueResult({ status: 'success', title: '重投请求已受理', message: `${deliveryTypeLabel[updated.deliveryType] ?? updated.deliveryType}已进入人工重投流程,当前状态为“${deliveryStatusLabel(updated)}”,人工重投次数 ${updated.manualRetryCount} 次。`, }); } else { const result = await adminApi.batchRequeueDownstreamDeliveries(requeueTarget.ids); const failures = result.results .filter((item) => item.status === 'failed') .map((item) => `${item.id}:${item.errorMessage || '重投失败'}`); setRequeueResult({ status: result.failedCount === 0 ? 'success' : result.successCount > 0 ? 'partial' : 'failed', title: result.failedCount === 0 ? '批量重投已受理' : result.successCount > 0 ? '批量重投部分完成' : '批量重投失败', message: `共处理 ${result.total} 条,成功 ${result.successCount} 条,失败 ${result.failedCount} 条。`, failures, }); setSelectedIds([]); } loadData(); } catch (failure) { setRequeueResult({ status: 'failed', title: '重投请求未确认成功', message: failure instanceof Error && failure.message ? failure.message : '人工重投失败,请先检查列表状态和系统日志,避免重复提交。', }); } finally { requeueInFlightRef.current = false; setRequeueBusy(false); } }; return (

下游投递记录

{error ?

{error}

: null}
投递总量 {summary?.total ?? 0} 当前筛选范围内的真实下游投递记录。
待投递 {summary?.pending ?? 0} 其中积压告警 {summary?.stalledPending ?? 0} 条。
客户端已确认 {summary?.delivered ?? 0} 已收到 Result=0 的 CMPP_DELIVER_RESP。
告警 {summary?.alertCount ?? 0} 近期失败 {summary?.recentFailed ?? 0} 条。
setKeyword(event.target.value)} placeholder="请输入关键字" value={keyword} /> { setDateRange(value); setPage(1); }} value={dateRange} /> { setStatus(event.target.value); setPage(1); }} /> ({ label: item.name, value: item.id })), ]} value={applicationId} onChange={(event) => { setApplicationId(event.target.value); setPage(1); }} />

类型分布

类型 总量 待投递 待确认 客户端已确认 未确认/拒绝 最终失败
{typeBreakdown.map((item) => (
{deliveryTypeLabel[item.deliveryType] ?? item.deliveryType} {item.total ?? 0} {item.pending ?? 0} {item.awaitingAck ?? 0} {item.delivered ?? 0} {(item.unconfirmed ?? 0) + (item.rejected ?? 0)} {item.failed ?? 0}
))}

重试压力

{retryBuckets.map((item) => (
{item.label} {item.count}
))}

应用告警排行

应用 待投递 异常 已投递 告警合计
{topApplications.length > 0 ? topApplications.map((item) => (
{item.name} {item.pending ?? 0} {(item.failed ?? 0) + (item.unconfirmed ?? 0) + (item.rejected ?? 0)} {item.delivered ?? 0} {item.alertCount ?? 0}
)) : (
暂无应用告警数据
)}

已选择 {selectedIds.length} 条;客户端已确认的记录也允许人工重投,请注意重复处理风险。

选择 投递信息 企业 / 应用 消息 ID 状态 重试 最后错误 操作
{records.length > 0 ? records.map((record) => (
{ setSelectedIds((current) => event.target.checked ? [...current, record.id] : current.filter((item) => item !== record.id)); }} aria-label={`选择${record.id}`} />
{deliveryTypeLabel[record.deliveryType] ?? record.deliveryType} {formatDateTime(record.createdAt)}
{record.tenant?.name ?? '-'} {record.application?.name ?? '-'}
{record.messageId ?? '-'}
{deliveryStatusLabel(record)}
{record.retryCount} 自动 / {record.manualRetryCount} 人工
{record.lastError ?? '无'}
)) : (
{loading ? '加载中...' : '暂无下游投递记录'}
)}
= totalPages} onPrevious={() => setPage((current) => Math.max(1, current - 1))} onNext={() => setPage((current) => Math.min(totalPages, current + 1))} />

后台重投任务

按筛选快照安全恢复,支持暂停、继续、终止和完整结果追踪。

setTaskRate(Number(event.target.value))} options={[{ label: '平稳(每应用10条/秒)', value: '10' }, { label: '快速(每应用20条/秒)', value: '20' }, { label: '低速(每应用5条/秒)', value: '5' }]} />