例:通过手机号登录
*首先获取分布式锁(分布式锁通过唯一key以手机号维度加锁)
rLock.tryLock(10, 10, TimeUnit.SECONDS)
waitTime等待获取锁的最大时间(阻塞时间),这里是 10 秒
leaseTime获取到锁之后,锁自动释放的时间(持有时间),这里是 10 秒。
unit时间单位,这里是 TimeUnit.SECONDS,即“秒”
目的:确保用户登录逻辑的线程安全和数据一致性,防止并发操作引发的问题
1.避免并发冲突
多个请求同时尝试使用同一个手机号,可能会导致并发问题,多次生成不同的 token,Redis 中缓存的数据被覆盖或不一致,使用分布式锁可以确保同一时间只有一个线程能执行登录逻辑,其他线程需等待锁释放.
2.保证数据一致性
在登录过程中,会涉及对 Redis 的多次读写操作(如查询旧 token、删除旧 token、设置新 token 等)。
如果这些操作没有原子性保障,可能导致中间状态暴露或数据错误。
加锁后,整个登录流程是串行化的,保证了 Redis 数据的一致性
3.防止资源竞争
比如在 doUserLoginByPhone 方法中,先检查是否存在旧 token,若存在则复用;否则生成新的 token 并写入 Redis。
若无锁保护,多个线程可能同时判断为“不存在旧 token”,从而生成多个 token,造成资源浪费或安全风险。
*然后获取token通过手机号
使用redissonClient,结合枚举加system和手机号组成唯一key
看RBucket是否存在,如果存在就获取旧token.将旧token删除,删除时
StringUtils.isNotEmpty(oldToken)
做空值判断
且删除登录信息-->
redissonClient.getBucket(RedisKeyEnum.USER_SYSTEM_LOGIN_KEY_PHONE, system, oldToken).delete();-->登录key通过唯一token确定手机号 redissonClient.getBucket(RedisKeyEnum.USER_SYSTEM_PHONE_USER_INFO, system, user.getPhone()).delete();-->用户信息缓存
*然后生成新token(未获取到token直接到这一步)
生成token通过
Phone,Salt,system组合MD5Utils.md5Encode
MD5 加密-->
System.currentTimeMillis(): 当前时间戳(毫秒),保证每次生成的 token 都不同。
含义:将系统名 + 手机号 + (盐值 + 时间戳) 的组合再次加密,生成一个基础 token 字符串。
含义:对手机号 + 时间戳再次 MD5 加密,生成一个附加字符串。
例:
String part1 = MD5("AI_PAINTING13800001111" + MD5("abcd1234" + 1717986912345L));
String part2 = MD5("13800001111" + 1717986912345L);
String token = part1 + part2;
将token设置回getTokenByPhoneBucket,并设置过期时间30天
结合token设置loginKeyBucket
通过phone设置USER_SYSTEM_PHONE_USER_INFO
然后返回token,释放锁
可将token设置至登录cookie中,结束