抽奖问题的复盘
一次“看起来没问题,实际抽奖错人”的排障复盘:Excel 导入、Unicode 与精确匹配
今天我们定位并修复了一个抽奖页的实际生产问题:在 excluded_teachers 已配置 张三 的情况下,张三 仍然被抽中。
一、现象
- 页面:
/lottery - 配置:
config/lottery.php中已配置excluded_teachers包含张三 - 操作:上传
名单.xls后抽 - 结果:
张三仍有机会被选中
二、先排除错误方向
最开始容易怀疑是配置文件写错位置(例如 log.php),但实际抽奖读取的是 lottery 配置:
- 后端把
config('lottery')注入前端 - 前端使用
excluded_teachers做过滤 - 所以问题不在
log.php
三、根因定位
根因不是“业务逻辑没写”,而是“字符串没有被正确拆分 + 精确匹配脆弱”。
- 成员列拆分正则存在编码风险
- 原逻辑依赖中文标点字面字符进行
split - 文件历史编码/复制过程可能导致“看起来是同一个符号,实际码点不同”
- 结果:如
张三,李四,王五被当成一个整体字符串,没有拆成 3 个人
- 后续过滤是精确匹配
- 过滤逻辑是
excludedSet.has(d.teacher) - 当
d.teacher变成整串时,当然匹配不到单独的张三
- 同类风险也存在于
fixedTeachers/fixedGroups
fixedTeachers之前是d.teacher === fixedNamefixedGroups之前是groupsMap.has(fixedGroupName)- 都会受到空格、全角空格、字符形似异码点等问题影响
四、修复方案
本次修复分三层,目标是“抗编码污染 + 抗空白差异 + 保持原业务行为”。
- 分隔符改为 Unicode 码位写法
- 将成员拆分正则改为:
/[\u3001\uFF0C,\s]+/ - 含义:按
、、,、,、空白拆分 - 好处:不依赖字面中文符号,减少编码转换导致的隐患
- 姓名与组名统一标准化后匹配
- 新增标准化函数,去除半角/全角空白等
excluded_teachers、fixed_teachers、fixed_groups均改为标准化后比较
- 组判重
usedGroups也改为标准化键
- 避免同组因为字符串形态差异被误判为不同组
五、为什么“编辑器看着正常”仍会出错
- 编辑器显示的是“渲染效果”,不是“底层码点”
- 正则和
split比较的是真实字符码点 - 所以会出现“肉眼一样,匹配失败”的情况
六、经验总结
- 不要把“看起来一样”当作“机器认为一样”
- 对中文标点分隔,优先使用 Unicode 码位表达
- 对关键匹配字段(人名、组名)先做标准化再比较
- 前端导入链路要有可观测性(建议保留 debug 日志或导入后样本检查)
七、这次问题的本质一句话
不是配置没写生效,而是“导入文本没有按预期拆分 + 精确匹配过于脆弱”,导致排除名单没有命中到真实候选人。