微信开发: code been used解决案例

1,259 阅读2分钟

1 异常描述:

wx授权登陆 无论是公众号网页授权还是小程序的code2session中都需要将用户的code兑换成openid//unionid//userInfo。这里经常会遇到的两个问题:

  • code been used code been used
  • invalid code 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 结果:

加了这段代码有一周多一点,服务器再也没报这个错误,证明是有效的