collect_status
claim_next
实测输出:
第 1 次点 已创建 1 个采集任务,等待客户端领取 第 2 次点 已创建 0 个采集任务(跳过 1 个采集中或已删除的) 弹窗「重新采集」 已创建 0 个采集任务(跳过 1 个采集中或已删除的) 商品状态: collecting ← 永远卡在这里
MarkCollecting 只在 pending / failed 时成功:
MarkCollecting
pending
failed
WHERE goods_id = ? AND deleted_at IS NULL AND collect_status IN ('pending', 'failed')
而全库只有四个地方会改 collect_status:
SetCollectResult
collected
SetCollectFailed
EnsurePddProduct
collecting
前两个只有客户端提交结果才会触发。所以一旦进了 collecting, 客户端不来提交就永远出不去,界面上没有任何入口能把它改回去。
客户端离线、崩溃、任务被删——这些都是常态,不是异常。 每发生一次就永久废掉一个商品,只能改数据库救。
而且提示「跳过 1 个采集中或已删除的」对操作员毫无信息量: 到底是在采、还是被删了、还是卡死了,看不出来,也不知道该等还是该做点什么。
做:
不做:
docs/client/00-glossary.md
tasks
WHERE goods_id = ? AND deleted_at IS NULL AND ( collect_status IN ('pending', 'failed') OR (collect_status = 'collecting' AND updated_at < ?) )
? 传 now - collectStaleAfter。
?
now - collectStaleAfter
[必须] 不要写后台协程定期把超时的改回 pending。 现算没有调度、 没有并发窗口;后台扫要处理"扫到一半客户端正好提交了"的竞态。 本项目已经在并发上栽过两次(PRAGMA 没作用到连接池、BEGIN DEFERRED 死锁), 能不引入并发就不引入。
[必须]
BEGIN DEFERRED
[必须] 字符串比时间是安全的,但依赖一个前提:model.NowISO() 永远产出 UTC 定宽格式(2026-08-07T06:10:19Z)。定宽 + 同时区 + 补零, 字典序才等于时间序。在 NowISO 上加一句注释说明有人依赖这个性质—— 以后谁改成带时区偏移的本地时间,这里的比较会静默失效,不报错但判断全错。
model.NowISO()
2026-08-07T06:10:19Z
NowISO
[必须] 定义成有名字的常量并写清理由,不要散落魔数:
// collectStaleAfter 是采集任务多久没动静就当它死了。 // // 采集本身几十秒到两分钟;加上排队等客户端来领,15 分钟足够宽裕。 // 宁可短也不要长:采集是**只读**操作,多采一次没有任何副作用, // 而卡死的代价是这个商品永久报废、只能改数据库救。 const collectStaleAfter = 15 * time.Minute
超时后允许重新建任务,但老任务还在队列里,客户端上线后可能把两个都领走, 采两次。这是可以接受的:
[必须] 把这条取舍写进代码注释和 docs/admin/03-data-model.md, 不要让后来人以为是 bug 而去"修"成加锁或加租约。
docs/admin/03-data-model.md
[必须] 超时的显示「采集中(超时)」,不是「采集中」。
理由:操作员盯着「采集中」不知道它已经死了,会一直等。 显示超时他才知道可以重新采。
[必须] 状态不能只靠颜色区分(docs/admin/05 §10 可访问性), 要有文字。
docs/admin/05
[建议] 筛选下拉里不新增「已超时」选项。它不是一个真实的存储状态, 只是「采集中」的一种呈现;加进去会让筛选和 collect_status 的取值对不上。
[建议]
现在不管什么原因都并成一句「跳过 N 个采集中或已删除的」。改成分类:
已创建 2 个采集任务,等待客户端领取 (跳过 1 个正在采集的、1 个已删除的)
[必须] 「正在采集」指没超时的那些。超时的现在会被正常创建,不再计入跳过。
[必须] 一个都没建成时不要只说「已创建 0 个」,要让操作员知道下一步该干嘛, 例如:没有创建任何任务:1 个正在采集中(还需等待约 12 分钟才可重试)。
没有创建任何任务:1 个正在采集中(还需等待约 12 分钟才可重试)
超时自动回退已经覆盖了这个场景,而且不需要操作员判断—— 他并不知道客户端是不是还活着、该不该取消。多一个按钮就多一处要维护、 多一处要写文档、多一处可能被误点。
如果将来发现 15 分钟太久、确实需要立刻重试,再单独开工单加。
admin/repository/pdd.go
admin/model/model.go
collectStaleAfter
admin/service/pdd.go
admin/handler/web/pdd.go
admin/templates/pdd/list.html
docs/admin/05-ui-specification.md
超时判定
updated_at
并发
界面
提示
其他
03-data-model.md
go vet
gofmt -l .
go test ./...
cd D:\chengma\cmautobuy\admin go vet ./... gofmt -l . go test ./... -count=1
端到端(不需要真机、不需要客户端):
time.Now()
回退:git revert。无表结构变更。
git revert
用户确认验收通过。归档状态已更新(3fdab7b)。
3fdab7b
e40ce5a
docs/task/24-admin-商品卡在采集中无法恢复.md
遗留:15 分钟边界的精确行为未单独测试;「可能采两次」在真实客户端上线后的表现未验证。
No dependencies set.
The note is not visible to the blocked user.
基本信息
collect_status的引入)要解决什么
复现步骤
claim_next,没有人会来领这个任务实测输出:
根因
MarkCollecting只在pending/failed时成功:而全库只有四个地方会改
collect_status:SetCollectResultcollectedSetCollectFailedfailedEnsurePddProduct复活分支pendingMarkCollectingcollecting前两个只有客户端提交结果才会触发。所以一旦进了
collecting,客户端不来提交就永远出不去,界面上没有任何入口能把它改回去。
为什么这是硬伤而不是小毛病
客户端离线、崩溃、任务被删——这些都是常态,不是异常。
每发生一次就永久废掉一个商品,只能改数据库救。
而且提示「跳过 1 个采集中或已删除的」对操作员毫无信息量:
到底是在采、还是被删了、还是卡死了,看不出来,也不知道该等还是该做点什么。
做什么 / 不做什么
做:
collecting超过一定时间视为已超时,允许重新创建采集任务不做:
(见
docs/client/00-glossary.md「为什么没有租约和心跳」),不得借这个工单偷偷加回来
tasks表结构怎么做
超时判定放在读取的那一刻现算,不要后台扫
?传now - collectStaleAfter。[必须]不要写后台协程定期把超时的改回pending。 现算没有调度、没有并发窗口;后台扫要处理"扫到一半客户端正好提交了"的竞态。
本项目已经在并发上栽过两次(PRAGMA 没作用到连接池、
BEGIN DEFERRED死锁),能不引入并发就不引入。
[必须]字符串比时间是安全的,但依赖一个前提:model.NowISO()永远产出UTC 定宽格式(
2026-08-07T06:10:19Z)。定宽 + 同时区 + 补零,字典序才等于时间序。在
NowISO上加一句注释说明有人依赖这个性质——以后谁改成带时区偏移的本地时间,这里的比较会静默失效,不报错但判断全错。
超时阈值取 15 分钟
[必须]定义成有名字的常量并写清理由,不要散落魔数:已知取舍:可能采两次
超时后允许重新建任务,但老任务还在队列里,客户端上线后可能把两个都领走,
采两次。这是可以接受的:
[必须]把这条取舍写进代码注释和docs/admin/03-data-model.md,不要让后来人以为是 bug 而去"修"成加锁或加租约。
列表页要看得见
[必须]超时的显示「采集中(超时)」,不是「采集中」。理由:操作员盯着「采集中」不知道它已经死了,会一直等。
显示超时他才知道可以重新采。
[必须]状态不能只靠颜色区分(docs/admin/05§10 可访问性),要有文字。
[建议]筛选下拉里不新增「已超时」选项。它不是一个真实的存储状态,只是「采集中」的一种呈现;加进去会让筛选和
collect_status的取值对不上。跳过提示按原因分类
现在不管什么原因都并成一句「跳过 N 个采集中或已删除的」。改成分类:
[必须]「正在采集」指没超时的那些。超时的现在会被正常创建,不再计入跳过。[必须]一个都没建成时不要只说「已创建 0 个」,要让操作员知道下一步该干嘛,例如:
没有创建任何任务:1 个正在采集中(还需等待约 12 分钟才可重试)。为什么不加「手工取消采集」按钮
超时自动回退已经覆盖了这个场景,而且不需要操作员判断——
他并不知道客户端是不是还活着、该不该取消。多一个按钮就多一处要维护、
多一处要写文档、多一处可能被误点。
如果将来发现 15 分钟太久、确实需要立刻重试,再单独开工单加。
预计修改文件
admin/repository/pdd.goMarkCollecting增加超时分支;列表查询带出「是否超时」admin/model/model.gocollectStaleAfter常量;NowISO加注释说明有人依赖定宽 UTCadmin/service/pdd.goadmin/handler/web/pdd.goadmin/templates/pdd/list.htmldocs/admin/03-data-model.mddocs/admin/05-ui-specification.md验收标准
超时判定
collecting且updated_at超过 15 分钟 → 能重新创建采集任务collecting但没到 15 分钟 → 仍然跳过,不重复建pending/failed→ 照常能建(原有行为不变)并发
(原有的原子抢占不能因为加了超时分支而失效)
界面
提示
其他
NowISO上有注释说明「有代码依赖它产出定宽 UTC,改格式会让时间比较静默失效」03-data-model.mdgo vet/gofmt -l ./go test ./...全过怎么验证
端到端(不需要真机、不需要客户端):
updated_at往前调 20 分钟风险和回退
NowISO改成本地时间,时间比较静默失效NowISO上加注释;测试用固定时间戳而非time.Now()回退:
git revert。无表结构变更。验收通过,关闭
用户确认验收通过。归档状态已更新(
3fdab7b)。e40ce5adocs/task/24-admin-商品卡在采集中无法恢复.md遗留:15 分钟边界的精确行为未单独测试;「可能采两次」在真实客户端上线后的表现未验证。