一个不在DEV团队的人,没有管理后台,没有生产数据库权限,也没有内部Slack。他了解DEV如何运转的全部途径,是读公开的forem/forem代码仓库。就凭这些,他诊断出了为什么DEV上没人能拿到多提交贡献者徽章,把服务端修复推了上去,还收到了@benhalpern本人的一句"Thank you for this"并完成合并。
![]()
事情从一次贡献冲刺开始。几天里他在forem/forem合并了四个拉取请求:修文档里断掉的Kamal目录锚点、清理管理表单里重复的aria-describedby属性、清理创作者设置里悬空的aria-describedby引用、给user_subscription标签的src加引号并补上缺失的alt。
第一个PR合并后,他的1x徽章(dev-contributor)立刻到账。第四个PR合并时,他从config/schedule.yml知道BadgeAchievements::BadgeAwardWorker每小时的第20分钟跑一次(cron为"20 * * * *")。他查了GitHub的Contributors API,确认自己的账号在第二页,contributions为4。因为Forem设置了Octokit.auto_paginate = true,这个worker肯定能翻到第二页。
一个小时的定时任务过去了,两个过去了,三个过去了,4x-commit-club徽章还是没来。他没有继续刷24小时个人主页,而是打开了app/services/badges/award_contributor_from_github.rb。
把负责1x徽章的award_single_commit_contributors和负责4x、8x、16x、32x的award_multi_commit_contributors放在一起对比,三个问题直接跳了出来:
- slug静默丢失:BADGE_SLUGS里每个键都通过Badge.id_for_slug(slug)解析,如果生产badges表里的slug和硬编码常量对不上,id_for_slug返回nil,服务对结果哈希调用.compact就继续往下走。没有报错,没有日志,一档徽章就这么悄悄消失。
- 缺少上下文消息:单提交徽章用first_or_create并带上rewarding_context_message_markdown,多提交徽章却只用了裸的.create,即便创建成功也漏掉了那句"Thank you so much for your contributions!"。四倍的提交,零次的感谢。
- 缺少幂等保护:create_badges_for_user每小时都对列表里每个贡献者重新尝试已获得的徽章,靠ActiveRecord的唯一性校验静默失败。如果某个徽章设了allow_multiple_awards: true,裸.create每小时都会插入一条重复成就,并触发通知和邮件。
关于第一个问题,他找到了关键线索。Forem只暴露resources :badges, only: [:index],没有公开的/badges/:slug详情路由,无法直接查询单个徽章slug。但翻公开的dev.to/badges目录时,他看到徽章#423的标题是"4x DEV Contributor Club",描述字段里却写着4x-commit-club。原因是Badge#generate_slug在没有显式slug时会把标题参数化,生产库里存的slug和硬编码的"4x-commit-club"根本对不上。
他开了Issue #24114,说明徽章ID 423到426的slug不匹配,并配上PR #24113修复服务本身:给多提交徽章补上rewarding_context_message_markdown;把裸的BadgeAchievement.create换成.where(...).first_or_create(...);把slug查找挪到GitHub认证提供方检查之后,并在配置的slug在数据库中缺失时加日志。
代码评审阶段出现了转折。@benhalpern账号下最初那份深入评审由Forem的自动评审代理(OpenHands)发出,它对badges表做了一次只读查询,确认了他的诊断:五个BADGE_SLUGS键里只有一个能解析,四个Nx-commit-club slug没有对应行,所以create_badges_for_user会跳过它们,今天没人能拿到这些徽章。
评审还抓出了他PR里的两个细节问题。第一,他原本在循环里对每个缺失slug打一条Rails.logger.warn,在生产slug对齐之前,DEV每小时会打4条warn;而Forem支撑着许多独立社区,多数自建实例开了GitHub认证却没播种DEV专属的贡献者徽章,每小时五条warn会把warn变成永久的背景噪音。修复方式是收集所有缺失slug,每次运行只打一条Rails.logger.info。
第二,他为幂等性写的回归测试在main上也能通过。徽章工厂默认allow_multiple_awards: false,在main上第二次.create会撞上唯一性校验静默失败,计数仍为1。这个测试测的不是first_or_create,而是ActiveRecord的唯一性校验器。他把测试改成用allow_multiple_awards: true创建徽章,并断言第二次运行时expect { described_class.call }.not_to change(BadgeAchievement, :count)。
推送修复后34项检查全绿,@benhalpern亲自留言"Thank you for this",随后把#24113以d1b962e合并进forem:main,这是他的第5个合并PR。不久后来自yo@dev.to的徽章邮件进了收件箱,4x DEV Contributor Club徽章落到他的主页上,带着他亲手接上的那句上下文消息。
他给过去的自己留了三条经验:回归测试不看到它在main上失败就不算数,先git stash再跑一遍;日志级别是运维契约,warn意味着有人该行动,可选数据缺失属于info;调试生产不需要生产权限,只需要公开源码、一个可证伪的假设,和顺着线索追下去的好奇心。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.