feat(pdd): 店铺连续同步失败 3 次后熔断,停止自动重试
背景:生产上「木塔网游专营店」Cookie 已过期,每约 24 秒重试一次、每轮刷一条 ERROR 日志,
持续数天。Cookie 失效不会自愈,继续重试只刷日志、白白占用同步周期。
行为:
- 失败 1~2 次:累计计数,继续重试;
- 第 3 次:熔断,打一条显式 ERROR(含"更新 Cookie 会自动恢复"提示);
- 熔断后:自动同步直接跳过,不再刷错误日志;
- 恢复:更新 Cookie(后台编辑或浏览器插件)自动解除;手动同步成功一次亦可。
熔断状态持久化在店铺配置里,容器重启后依然生效。
改动:
- pdd-source-config-service:阈值常量 + consecutiveFailures/syncSuspendedAt/
syncSuspendedReason 持久化字段 + 纯函数 resolvePddStoreFailureState /
applyPddStoreBreakerState / isPddStoreSyncSuspended / shouldResetPddSyncBreaker。
- pdd-sync-service:syncAllPddStores 跳过熔断店铺并上报 suspendedCount;
syncStoreOrders 失败记录、成功清零(健康店铺不产生额外配置写)。
- pdd-order-sync-job:上报熔断数,且存在熔断店铺时强制留在 INFO 级
(否则"全部店铺已停同步"会因新单为 0 被降级成 debug 而不可见)。
- 前端加「已熔断 · 停止重试」标签与类型字段。
顺带修掉一个隐患:saveAdminPddStore 收的是 Partial 载荷,normalizePddStoreItem
会为缺失字段产出 undefined,导致一次「只改店铺名」的保存就把熔断洗掉。
由 applyPddStoreBreakerState 回填既有状态解决。
验证:9 项熔断单测(含局部保存不洗状态、Cookie 变更解除熔断)。
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
SafetyCertificateOutlined,
|
||||
SaveOutlined,
|
||||
ShopOutlined,
|
||||
StopOutlined,
|
||||
ThunderboltOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import {
|
||||
@@ -541,12 +542,25 @@ export function PddPlatformPanel() {
|
||||
const totalOrders = testRes ? testRes.totalOrders : record.totalOrders
|
||||
const timeDisplay = testRes?.testedAt || formatDisplayTime(record.lastCheckedAt)
|
||||
|
||||
// 连续失败已熔断:自动重试已经停了。必须显式提示,否则客服会以为只是
|
||||
// "暂时失效",不知道新订单已经不再进来了。
|
||||
const suspendedTag = record.syncSuspendedAt ? (
|
||||
<Tooltip
|
||||
title={`连续失败 ${record.consecutiveFailures ?? 3} 次后已停止自动重试(${formatDisplayTime(record.syncSuspendedAt)})。原因:${record.syncSuspendedReason || '未知'}。更新 Cookie 后会自动恢复。`}
|
||||
>
|
||||
<Tag icon={<StopOutlined />} color="volcano" style={{ cursor: 'pointer', margin: 0 }}>
|
||||
已熔断 · 停止重试
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
) : null
|
||||
|
||||
if (isValid === true) {
|
||||
return (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Tag icon={<CheckCircleOutlined />} color="success">
|
||||
有效 (在售: {totalOrders ?? '-'})
|
||||
</Tag>
|
||||
{suspendedTag}
|
||||
{timeDisplay && (
|
||||
<Text type="secondary" style={{ fontSize: '11px' }}>
|
||||
{timeDisplay} 校验
|
||||
@@ -569,6 +583,7 @@ export function PddPlatformPanel() {
|
||||
失效 (已告警)
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
{suspendedTag}
|
||||
{timeDisplay && (
|
||||
<Text type="secondary" style={{ fontSize: '11px' }}>
|
||||
{timeDisplay} 校验
|
||||
@@ -583,7 +598,12 @@ export function PddPlatformPanel() {
|
||||
)
|
||||
}
|
||||
|
||||
return <Badge status="default" text={<Text type="secondary">未校验</Text>} />
|
||||
return (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Badge status="default" text={<Text type="secondary">未校验</Text>} />
|
||||
{suspendedTag}
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,6 +17,12 @@ export interface AdminPddStoreItem {
|
||||
totalOrders?: number
|
||||
syncToken?: string
|
||||
lastExtensionSyncAt?: string
|
||||
/** 连续同步失败次数;成功一次即清零。 */
|
||||
consecutiveFailures?: number
|
||||
/** 熔断时间(ISO)。存在即表示已停止自动重试,更新 Cookie 后自动恢复。 */
|
||||
syncSuspendedAt?: string
|
||||
/** 触发熔断时的最近一次失败原因。 */
|
||||
syncSuspendedReason?: string
|
||||
}
|
||||
|
||||
export interface AdminPddStoreSyncTokenResponse {
|
||||
@@ -57,5 +63,7 @@ export interface AdminPddSyncAllResponse {
|
||||
enabled: boolean
|
||||
storeCount: number
|
||||
syncedCount: number
|
||||
/** 因连续失败被熔断、本轮跳过自动重试的店铺数。 */
|
||||
suspendedCount?: number
|
||||
results: AdminPddSyncResult[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user