如何基于经纬度生成4位验证码?满足同城ADD CODE短时无冲突需求
嘿,这个需求挺贴合线下场景的,咱们来聊聊怎么用4位验证码搞定这个问题,核心思路是把4位码的唯一性约束缩小到「城市+短时间窗口」这个局部范围内,毕竟你不需要全局唯一,只要同一城市同一时段不重复就行。下面是几个可落地的方案:
核心思路铺垫
4位码总共10000种组合,只要每个城市在设定的时段(比如5分钟)内的并发生成请求数不超过10000,就完全可以做到无冲突。先明确几个前置规则:
- 每个ADD CODE绑定城市ID+时间窗口,过期自动失效(比如5分钟后就不能用了)
- 校验好友时,除了比对ADD CODE和距离,还要确认双方的时间窗口是否重叠(或完全匹配)
具体实现方案
1. 基于城市+时间窗口的随机池方案
这是最容易实现的方案:
- 把时间切成固定窗口(比如5分钟),用
城市ID + 时间窗口标识作为唯一键(比如city_123_window_1699992000,其中时间窗口标识是当前时间戳除以300取整) - 用Redis的Set结构存储该键下已使用的4位码,生成时随机生成一个4位字符串,检查是否在Set里:
- 如果不在,就把它加入Set,并给这个键设置过期时间(等于窗口时长)
- 如果存在,就重新生成,直到找到未使用的码
- 给重试加个上限(比如5次),如果还是冲突,直接返回"当前请求过多,请稍后再试"(这种情况在并发远低于10000时几乎不会发生)
示例伪代码:
function generateAddCode(cityId) { const window = Math.floor(Date.now() / 300000); // 5分钟窗口,单位毫秒 const redisKey = `add_code:${cityId}:${window}`; let retry = 5; while (retry > 0) { const code = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); const isNew = await redis.sadd(redisKey, code); if (isNew) { await redis.expire(redisKey, 300); // 5分钟后自动清理 return code; } retry--; } throw new Error('无法生成可用验证码,请稍后重试'); }
2. 城市内自增循环码方案
如果担心随机生成的重试问题,可以用自增逻辑:
- 每个城市维护一个自增计数器,从0000开始到9999循环,每次生成码时取当前计数器值,然后原子性+1
- 同样绑定时间窗口,每个窗口开始时计数器重置为0000
- 集群环境下要用Redis的
INCR原子操作来保证计数器唯一,避免多实例冲突
这个方案的好处是完全不会有冲突,只要同一窗口内的请求数不超过10000,就稳定可用。
3. 优化体验的小技巧
如果想进一步降低冲突概率或提升用户输入体验:
- 排除易混淆数字:比如去掉0、1、6、9,只用2-5、7-8,这样虽然可用码降到6^4=1296种,但用户输入错误率大幅降低,适合并发不高的场景
- 动态调整窗口时长:比如根据城市的实时请求量,自动缩短或延长窗口(比如热门城市用3分钟窗口,冷门城市用10分钟),平衡冲突概率和码的有效期
校验环节的补充逻辑
好友匹配时,除了比对ADD CODE和城市ID,还要:
- 检查B输入的ADD CODE是否还在有效期内(即对应的时间窗口是否未过期)
- 确认A和B的位置距离符合要求
- 匹配成功后,记得把这个ADD CODE标记为已使用,避免被其他用户重复利用
内容的提问来源于stack exchange,提问作者Elm Liu
相关产品推荐
相关产品推荐

