微信小程序获取用户手机号

文档资料:

        微信官方文档获取ACCESS_TOKEN:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html微信开发者平台文档https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html        微信官方文档根据code及ACCESS_TOKEN获取手机号:

获取手机号 | 微信开放文档微信开发者平台文档https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html

1:获取流程

        1.1:预先申请好的小程序APPID及SECRET(在小程序管理员中可以看到)

        1.2:通过APPID及SECRET获取到微信服务器上该小程序的ACCESS_TOKEN

        1.3:前端获取到微信用户的code

        1.4:后端接收到前端传输的code,使用code及ACCESS_TOKEN获取到微信用户的手机号

  2:代码片段

/**
 * 微信相关业务层
 */
@Service
public class WxServiceImpl implements IWxService {
    private static Logger logger = LoggerFactory.getLogger(WxServiceImpl.class);

    // APPID
    @Value("${wechat.appid}")
    private String appId;
    // SECRET
    @Value("${wechat.secret}")
    private String appSecret;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    /** 微信小程序获取Access_Token地址 */
    public static String WX_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET";

    /** 微信小程序获取手机号地址 */
    public static String WX_SJH_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN";

    /** access_token 缓存Key */
    public static String REDIS_ACCESS_TOKEN_KEY = "qdfp:wx:accesstoken";

    /** access_token 缓存击穿Lock */
    public static String REDIS_ACCESS_TOKEN_LOCK = "qdfp:lock:accesstokenlock";


    /**
     * 获取ACCESS_TOKEN 及 ACCESS_TOKEN刷新策略
     * */
    @Override
    public String getAccessToken() {

        // 判断缓存中是否有数据
        String token = stringRedisTemplate.opsForValue().get(REDIS_ACCESS_TOKEN_KEY);
        if(StrUtil.isNotBlank(token)){ // 缓存命中直接返回ACCESS_TOKEN
            return token;
        }

        try {
                // 缓存失效、重建缓存
                if(!tryLock(REDIS_ACCESS_TOKEN_LOCK)){ // 此处没有获取到锁,表示其他线程获取到锁在执行缓存重建
                    Thread.currentThread().sleep(1000); // 休眠1000ms,避免一直重试获取锁
                    return getAccessToken(); // 递归尝试获取锁
                }

                // 获取锁成功、重建缓存
                token = stringRedisTemplate.opsForValue().get(REDIS_ACCESS_TOKEN_KEY);

                if(StrUtil.isBlank(token)){ // 缓存中没有数据、重建缓存
                    // 缓存中没有ACCESS_TONKEN信息,去调用微信接口获取ACCESS_TOKEN
                    HttpResponse response = HttpRequest.get(
                                            WX_TOKEN_URL
                                            .replaceAll("APPID", appId)
                                            .replaceAll("SECRET", appSecret)
                                            ).execute();

                    if(response.getStatus() == 200){
                        Map repMap = JSONUtil.toBean(response.body(), Map.class);
                         token = (String) repMap.get("access_token");
                         // 将获取到的ACCESS_TOKEN写入缓存,并设置110分钟的过期时间(官方文档ACCESS_TOKEN失效时间为120分钟,程序将在110分钟的时间进行刷新)
                        stringRedisTemplate.opsForValue().set(REDIS_ACCESS_TOKEN_KEY,token,110L, TimeUnit.MINUTES);
                    }
                }
        }catch (InterruptedException e){
            e.printStackTrace();

        }finally {
            // 释放锁
            unLock(REDIS_ACCESS_TOKEN_LOCK);
        }
        return token;
    }



    /**
     * 通过code换取微信手机号
     */
    @Override
    public Map getWxsjh(Map map) {
        // 参数校验
        AssertUtils.isNotBlank((String)map.get("code"),"code不能为空");

        // 用于封装响应结果
        Map<String, Object> resultMap = new HashMap<>();

        // 获取access_token
        String accessToken = getAccessToken();

        // 重新构建code参数
        Map param = new HashMap<>();
        param.put("code",map.get("code"));


        // 构建获取微信手机号请求
        HttpResponse response = HttpRequest.post(
                WX_SJH_URL.replaceAll("ACCESS_TOKEN", accessToken)
        ).body(JSONUtil.toJsonStr(param)).execute();

        // 返回响应
        if(response.getStatus() != 200){
            throw new IMsgEx("获取手机号异常,请稍后再试");
        }

        JSONObject repObj = JSONUtil.parseObj(response.body());

        // 获取微信响应错误码
        Integer errcode = repObj.getInt("errcode");

        if(errcode != 0){ // 响应异常
            throw new IMsgEx(repObj.getStr("errmsg"));
        }

        Map phoneInfoMap = repObj.get("phone_info", Map.class);
        // 获取没有区号的手机号
        String purePhoneNumber = (String) phoneInfoMap.get("purePhoneNumber");
        AssertUtils.isNotBlank(purePhoneNumber,"获取手机号失败,请稍后再试");
        resultMap.put("purePhoneNumber",purePhoneNumber);
        return resultMap;
    }





    /**
     *  获取锁方法 使用Redis的SETNX命令实现锁
     *      SETNX命令解释:
     *              往Redis中添加 K - V 键值数据,当添加的Key在Redis中不存在时
     *              才添加成功,返回0/1代表添加失败/成功。
     * */
    private boolean tryLock(String key){
        // 为锁设置的过期时间是需要大于缓存重建的时间,设置过期时间的目的是防止程序异常出现“死锁”问题
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag); // 使用Hutool工具类将Bolean类型转bolean
    }

    /** 释放锁方法 即删除锁 */
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }
}

说明:

        对于ACCESS_TOKEN的刷新没有采用轮询程序定时去更新的方式刷新ACCESS_TOKEN,通过Redis的SET NX命令实现分布式锁 + 为Key设置过期时间 让用户调用获取手机号码接口时自动刷新ACCESS_TOKEN。

微信官方对ACCESS_TOKEN刷新的部分说明:

        ACCESS_TOKEN的有效期通过返回的 ACCESS_TOKEN来传达,目前是7200秒之内的值,中控服务器需要根据这个有效时间提前去刷新。在刷新过程中,中控服务器可对外继续输出的老 access_token,此时公众平台后台会保证在5分钟内,新老 access_token 都可用,这保证了第三方业务的平滑过渡;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值