1 异常描述:
wx授权登陆 无论是公众号网页授权还是小程序的code2session中都需要将用户的code兑换成openid//unionid//userInfo。这里经常会遇到的两个问题:
- code been used
- invalid code
2 产生原因和处理方案:
网上查了一些资料。有人认为是微信服务器中断了一次连接,发起了第二次带来的。也有人认为是前端并发过来的请求。这里因为涉及到实际上的用户行为,具体客观分析原因比较难。但解决这个问题的思路不难,因为本质上就是有两个相同的code发到了我们的服务器,code是一次性的,消耗掉了第一个再处理第二个的时候自然会报code been used异常。针对这种情况 最好的思路其实就是去重。下面提供一段代码:
思路就是用redis存第一个code处理的结果,第二个过来去缓存里读,不再向微信发起请求。 当然这里还考虑到第二个请求可能非常快就过来了,这个时候第一个code还没来得及兑换成openId和unionId,加了一段重试的代码。
public GetSessionResultVO getSessionResult(GetSessionResultDTO getSessionResultDTO) {
try {
final String SPLIT = "_";
final Long WAIT_TIME = 1000L;
String code = getSessionResultDTO.getCode();
//1 key:code value:code 用于检查是否有两个相同的code
String codeKey = String.format(PinyinCacheConstants.Biz.WX_CODE, code);
//2 key:code value: openId_unionId 用于在缓存中读取值
String valueKey = String.format(PinyinCacheConstants.Biz.WX_USER_SESSION, code);
Boolean cacheExists = xiaozaoRedisCache.getValueOps()
.setIfAbsent(codeKey, code, 60, TimeUnit.SECONDS);
log.info("codeKey:{}, valueKey:{}, cacheExists:{}", codeKey, valueKey, cacheExists);
//3 如果key被占用 去缓存中读取值
if (ObjectUtil.isNull(cacheExists) || Boolean.FALSE.equals(cacheExists)) {
log.info("setNx = false 从缓存中读取值");
int retryTimes = 0;
int maxRetryTimes = 2;
do {
log.info("重试次数:{}", retryTimes);
try {
String voStr = (String) xiaozaoRedisCache.getValueOps().get(valueKey);
log.info("voStr:{}", voStr);
if (StrUtil.isNotEmpty(voStr) && (voStr.contains(SPLIT))) {
String openId = voStr.split(SPLIT)[0];
String unionId = voStr.split(SPLIT)[1];
log.info("从缓存中读取到的值 openId:{} unionId:{}", openId, unionId);
return new GetSessionResultVO().setOpenId(openId).setUnionId(unionId);
}
log.warn("从缓存中获取session Result失败,{} ms 后重试(第{}次)", WAIT_TIME, retryTimes + 1);
Thread.sleep(WAIT_TIME);
} catch (Exception e) {
log.warn("从缓存中获取session Result失败,{} ms 后重试(第{}次)", WAIT_TIME, retryTimes + 1);
}
} while (retryTimes++ < maxRetryTimes);
}
WxMaService wxMaService = WxMaConfiguration.getMaService(getSessionResultDTO.getAppId());
// 4 获得用户openId, unionId, sessionKey
WxMaJscode2SessionResult sessionResult =
wxMaService.getUserService().getSessionInfo(getSessionResultDTO.getCode());
log.info("sessionResult:{}", sessionResult);
String sessionKey = sessionResult.getSessionKey();
String openId = sessionResult.getOpenid();
String unionId = sessionResult.getUnionid();
// 5 缓存 sessionKey
String redisKey = String.format(PinyinCacheConstants.Biz.MA_SESSION, openId);
xiaozaoRedisCache.setEx(redisKey, sessionKey, MA_SESSION_EXPIRE);
// 6 存入redis
log.info("code2session读取到的值 openId:{} unionId:{}", openId, unionId);
String value = String.format("%s_%s", openId, unionId);
xiaozaoRedisCache.getValueOps()
.set(valueKey, value, 60, TimeUnit.SECONDS);
return new GetSessionResultVO().setOpenId(openId).setUnionId(unionId);
} catch (Exception e) {
log.error("获取小程序用户登录会话秘钥失败===== 错误原因:{}", e);
throw new BusinessException(WxErrorCode.USER_SESSION_FAILED);
}
}
3 结果:
加了这段代码有一周多一点,服务器再也没报这个错误,证明是有效的