v0.4.0:窗口调度/限流分类/可观测性,抢购改每账号独立循环,移除 CLI 只保留 GUI

This commit is contained in:
yml2213
2026-10-16 18:14:30 +08:00
parent 29aed15e4e
commit 8b3b14654f
20 changed files with 2309 additions and 664 deletions
+29 -7
View File
@@ -97,9 +97,12 @@ type UiConfig = {
minLeft: number
cookieText: string
selectedAccountLines: number[]
// loops 是轮次上限:0(留空)表示按窗口运行到窗口结束。
loops: number
delayMs: number
concurrency: number
// windowSeconds 是抢购窗口秒数(窗口模式下跑到「开抢+窗口」才收工)。
windowSeconds: number
stopAfterSuccess: boolean
stopAccountAfterSuccess: boolean
prewarm: boolean
@@ -114,6 +117,10 @@ type UiConfig = {
errorBackoffMs: number
timeoutSec: number
startAt: string
// adaptiveThrottle 是「全局自适应降速」开关(表单用正语义)。
adaptiveThrottle: boolean
// disableAdaptiveThrottle 只发给后端(后端零值=开启,所以用反语义字段),不落本地存储。
disableAdaptiveThrottle: boolean
}
type AccountPreview = {
@@ -301,7 +308,7 @@ function exchangeOrderID(exchange?: ExchangeResp): string {
return exchange?.order_id || exchange?.orderId || ''
}
type SavedConfig = Omit<UiConfig, 'cookieText' | 'selectedAccountLines'>
type SavedConfig = Omit<UiConfig, 'cookieText' | 'selectedAccountLines' | 'disableAdaptiveThrottle'>
type ScheduleCountdown = {
targetAt: number
@@ -466,6 +473,10 @@ function categoryTag(category?: string): ReactNode {
condition: { color: 'geekblue', label: '条件' },
failed: { color: 'warning', label: '失败' },
error: { color: 'error', label: '异常' },
// protocol 是回包结构异常(缺 tRsp/tResp),单独归类便于排查服务端错误信封。
protocol: { color: 'error', label: '协议异常' },
// session_lost 是「账号未绑定」这类瞬时会话失效,通常下一轮就能恢复。
session_lost: { color: 'volcano', label: '会话失效' },
}
if (!category) return <Tag>待处理</Tag>
const item = map[category] || { color: 'default', label: category }
@@ -583,9 +594,10 @@ function pickConfig(values: Partial<UiConfig>): UiConfig {
minLeft: Number(values.minLeft || 0),
cookieText: values.cookieText || '',
selectedAccountLines: values.selectedAccountLines || [],
loops: Number(values.loops || 1),
loops: Number(values.loops || 0),
delayMs: Number(values.delayMs || 150),
concurrency: Number(values.concurrency || 1),
windowSeconds: Number(values.windowSeconds || 30),
stopAfterSuccess: Boolean(values.stopAfterSuccess),
stopAccountAfterSuccess: values.stopAccountAfterSuccess !== false,
prewarm: values.prewarm !== false,
@@ -599,6 +611,8 @@ function pickConfig(values: Partial<UiConfig>): UiConfig {
errorBackoffMs: Number(values.errorBackoffMs || 300),
timeoutSec: Number(values.timeoutSec || 15),
startAt: values.startAt || '',
adaptiveThrottle: values.adaptiveThrottle !== false,
disableAdaptiveThrottle: values.adaptiveThrottle === false,
}
}
@@ -857,7 +871,7 @@ export default function Dashboard() {
const errorCount = useMemo(
() =>
results.filter((item) =>
['error', 'failed', 'not_enough', 'off_shelf', 'sold_out', 'condition'].includes(
['error', 'failed', 'not_enough', 'off_shelf', 'sold_out', 'condition', 'protocol'].includes(
item.category || '',
),
).length,
@@ -1134,6 +1148,7 @@ export default function Dashboard() {
loops: values.loops,
delayMs: values.delayMs,
concurrency: values.concurrency,
windowSeconds: values.windowSeconds,
stopAfterSuccess: values.stopAfterSuccess,
stopAccountAfterSuccess: values.stopAccountAfterSuccess,
prewarm: values.prewarm,
@@ -1147,6 +1162,7 @@ export default function Dashboard() {
errorBackoffMs: values.errorBackoffMs,
timeoutSec: values.timeoutSec,
startAt: values.startAt,
adaptiveThrottle: values.adaptiveThrottle,
}
localStorage.setItem(scopedKey(configStorageBase), JSON.stringify(saved))
message.success('已保存')
@@ -1966,8 +1982,11 @@ export default function Dashboard() {
<Form.Item name="minLeft" label="最少库存">
<InputNumber min={0} />
</Form.Item>
<Form.Item name="loops" label="次数">
<InputNumber min={1} max={9999} />
<Form.Item name="loops" label="次数上限" tooltip="留空或 0 = 按窗口运行到窗口结束;填了就作为轮次上限">
<InputNumber min={0} max={9999} placeholder="0=按窗口" />
</Form.Item>
<Form.Item name="windowSeconds" label="窗口秒" tooltip="窗口模式下跑到「开抢 + 窗口秒数」才收工;开抢后约 9 秒内库存就会见底,默认 30 秒留足库存回流余量">
<InputNumber min={1} max={600} addonAfter="s" />
</Form.Item>
<Form.Item name="delayMs" label="延时">
<InputNumber min={10} max={60000} addonAfter="ms" />
@@ -1975,8 +1994,8 @@ export default function Dashboard() {
<Form.Item name="concurrency" label="线程">
<InputNumber min={1} max={100} />
</Form.Item>
<Form.Item name="leadMs" label="提前">
<InputNumber min={0} max={10000} addonAfter="ms" />
<Form.Item name="leadMs" label="提前" tooltip="留空或 0 = 按服务端时钟校准误差动态计算(通常几十毫秒);填了就用固定提前量">
<InputNumber min={0} max={10000} addonAfter="ms" placeholder="0=动态" />
</Form.Item>
</div>
<div className="settings-switch-grid">
@@ -2001,6 +2020,9 @@ export default function Dashboard() {
<Form.Item name="timeSync" label="时间校准" valuePropName="checked" tooltip="定时前用 pingSignal 校准服务端时间,按钟差对齐触发时刻">
<Switch />
</Form.Item>
<Form.Item name="adaptiveThrottle" label="自适应降速" valuePropName="checked" tooltip="连续十几个请求都拿不到业务响应时整体降速,缓解同机同 IP 的限流风暴">
<Switch />
</Form.Item>
</div>
</div>
</Modal>