NewMax 跑长任务跑到一半失败,重试的起点一般不是第 1 步,而是「最后一个在系统里已经生效的动作」的下一步。要确定这个位置,得回到任务的执行记录,逐个核对已执行动作留下的痕迹:读取动作看有没有取到数据,导航动作看页面 URL 是否真的变了,提交类动作则要看目标数据在页面上是否已经出现。状态字段写着 ok,不代表业务侧已经落库;状态字段写着 fail,也不代表提交没有到达对方系统,这两点决定了重试该接哪一步。
长任务失败后的重试起点,通常按「最后一个已生效动作 + 1」来定,不要默认回到第一步。读取、导航、切换筛选属于可重放动作,重跑一遍代价小、不改业务状态;提交、发送、删除属于不可重放动作,重试前必须按业务键先去页面或列表里查证是否已经完成。适用边界:执行记录的状态字段只能作参考,生效与否要靠页面结果类证据判断;遇到无法查证的不可重放动作,建议先在测试数据上做一次故意中断的验证,再决定整段是否重跑。
在记录里确认失败发生在第几步、之前几步有没有生效
先打开 NewMax 的任务运行记录,找到失败那一次执行,按 step 编号逐条往下读。需要重点看四类信息:步骤序号、动作类型、状态、以及动作的输入参数快照。下面是一段示意格式,真实字段名以你环境的记录页为准,重点是把同名段落对齐着读:
step 07 navigate /orders/list ok 12:04:11
step 08 set_filter status=pending ok 12:04:15
step 09 open_row order_id=88213 ok 12:04:22
step 10 submit form=refund fail 12:04:40 timeout
判断「有没有生效」的依据不能只看 ok/fail。navigate 是否生效,看失败后当前页面 URL 是不是 /orders/list;set_filter 是否生效,看列表是否只剩 pending 的行;open_row 是否生效,看详情页显示的订单号是不是 88213。提交类动作最需要小心:timeout 只说明等回执超时,请求可能已经到达对方系统。
页面结果类证据的取法:在失败停住的位置手动刷新一次页面或重新进入目标列表,把与本次任务相关的业务键(订单号、消息 ID、目标对象名)逐个查一遍,记录条数、状态字段、创建时间、最近一次操作时间。截图或复制这几项到重试记录里,后面验证「有没有重复提交」时要用同一组字段做对比。
在动手之前先算一次重试代价:从失败步继续,需要重做的动作数是多少;从第 1 步重来,会重复触发几个不可重放动作。如果答案里出现了提交、发送、删除,就不要图省事整段重跑。
把动作分成可重放和不可重放两类
重试能不能直接再跑一遍,取决于动作本身会不会改变对方系统的状态,和 NewMax 的重试按钮怎么设计关系不大。
- 可重放:读取(查询列表、拉取详情、下载文件)、导航(打开页面、跳转下一屏)、切换筛选与排序分页。理由很直接:这类动作只改变「看到了什么」,重复执行不会多出一条业务记录,最多浪费一点时间。失败后可以直接从这些步骤接上。
- 不可重放:提交表单、发送消息或邮件、删除或撤销、审批通过、支付类操作。理由同样直接:每执行一次,对方系统就多一次业务事件,重放会产生重复记录、重复通知,或者把已被处理的对象再改一遍。这类动作不能靠「再点一次」解决。
不可重放动作的替代处理是「先查证、后执行」:用业务键去目标列表或详情页确认结果是否已经存在。以退款提交为例,先按订单号查该订单当前的退款状态和退款单号,已有记录就把这一步标记为已完成、直接跳到下一步;没有记录才重新提交。如果任务配置里支持传入幂等键或请求标识,重试时优先沿用同一个键值,让对方系统自己识别重复请求;配置里没有这个能力,就只能靠查证这一层兜底。
把长任务拆成可断点的小段
拆段的目的是让任务描述本身就支持从中间接上,而不是每次失败都靠人去翻日志推断。拆段原则可以概括成三句:一个不可重放动作配它的前置读取为一组;每组结束时要产出一个可记录的业务标识;每组的开头都要能独立复原页面状态,不依赖上一组停在哪个页面。
段与段之间需要保留的状态通常是:当前处理到的业务键或游标、已提交成功的记录编号列表、本段入口 URL 与筛选条件、剩余待处理队列。这些状态要么写进任务变量,要么落在外部文件里,重试时由 resume_from 读回来。
task: refund_pending_orders
segments:
- id: s1_collect
start: { url: /orders/list, filter: status=pending }
do:
- read: rows[0..n] -> order_ids
emit: { order_ids: list }
- id: s2_refund
foreach: order_ids
precheck: { url: /orders/{id}, assert_field: refund_status }
do:
- open: /orders/{id}
- submit: { form: refund, idempotency_key: "{id}-refund" }
emit: { last_done: "{id}" }
resume_from: last_done
上面是通用骨架,字段名要替换成你环境里实际的任务配置项。precheck 这一段是关键:它把「查证」写进了任务描述,重试跑到 s2 时不会盲目再提交一次。断点位置建议按业务键记录,不要按页面坐标或者行序号记录,列表刷新后序号会变,业务键不会。
重试前先复原页面状态
失败那一刻的页面通常处于半完成状态:表单填了一半、弹窗还开着、筛选条件被中途的操作改过。直接在残留状态上点重试,下一步动作很可能落在错误的元素上,或者把上一次没提交完的内容一起带上。
- 回到起始页:重新导航到该段声明的入口 URL,不要用浏览器后退按钮,后退会带回历史表单值。到达后确认 URL 与段配置一致。
- 清空输入框:逐个清空搜索框、数量、备注这类可编辑字段,再重新应用一次筛选参数。表单值被自动填充时,宁可重建页面也不要手工擦除。
- 关掉中途弹出的指引层:引导浮层、升级提示、同意框经常在长任务中途出现并挡住下一步的点击。通用做法是先按关闭按钮的稳定标识去关,关不掉再按 ESC,最后确认遮罩是否消失、页面是否可点。
把这几步做成固定的「就绪检查」,重试前跑一遍:记下当前 URL、当前筛选条件、可见弹窗数量。三项和预期不符就先修复页面状态,不要带着异常直接进业务动作。
用一次故意中断验证重试不会重复提交
「不会重复」只有被观察到才算成立。建议挑一条测试数据,在不可重放动作执行之后、下一步开始之前制造一次中断:手动停止任务、断开页面连接、或者关闭当前的浏览器上下文。中断后先不要动,按前面的方法把页面证据记录一遍。
重试之后需要核对的页面证据包括:目标对象详情里这类记录只有一条;对应列表的总条数只增加了一条;创建时间只有一个、没有前后相差很近的两条;如果系统展示消息或流水号,确认没有两个不同的编号指向同一次业务操作。这些字段最好是重试前后各取一次,用同一套字段对比,而不是靠印象判断。
如果核对时发现了重复提交,处理顺序是:先停掉任务,避免继续放大;记录重复记录的编号和创建时间;能用业务侧取消、关闭、作废接口处理的,把多余的那条处理掉并留痕;不能撤销的,人工标注后回头改任务描述,把该动作从「直接执行」改成「先查证再执行」,并在允许的情况下补上幂等键。改完之后再重复一次同样的中断验证,确认第二次重试没有新增记录,才把这段任务放回常规队列。