Uniapp Record:获取手机号

489 阅读6分钟

前言:渠道 -> 产品:"我需要收集用户信息"。产品 -> 开发:"那就新增一个功能来获取用户的手机号地址相关信息"。最近项目中增加了获取用户信息相关需求,这个功能怎么说呢,对于我甚至是大部分人来说都是比较抵触的吧,毕竟无缘无故获取个人信息就是感觉不爽,哈哈!但是没也没法,身为打工人的无奈,照做呗。

由于目前项目技术栈是 uniapp,所以先去官方文档查阅相关资料,了解到目前有是三种方式涉及手机号相关的,自然也是能够获取到手机号。

1. uni一键登录

uni一键登录是DCloud公司联合个推公司推出的,整合了三大运营商网关认证能力的服务。实现流程如下:

  1. App 界面弹出请求授权,询问用户是否同意该App获取手机号。这个授权界面是运营商SDK弹出的,可以有限定制;
  2. 用户同意授权后,SDK底层访问运营商网关鉴权,获得当前设备access_token等信息;
  3. 在服务器侧通过 uniCloud 将access_token等信息 置换为当前设备的真实手机号码。然后服务器直接入库,避免手机号传递到前端发生的不可信情况。

对该方法大致了解了下,其中流程相对比较简单,但是结合当前项目来说:

  1. 每次验证需要收费,虽然很便宜(2分)
  2. 需要开通uni一键登录服务,uniCloud 服务

因为项目不涉及云开发,而且不考虑产品使用时产生的额外费用,所以暂时pass掉。

2. OAuth 登录鉴权

App端OAuth(登录鉴权)模块封装了市场上主流的三方登录SDK,提供JS API统一调用登录鉴权功能。也看下实现流程:

  1. 向三方登录平台申请开通,有些平台(如微信登录)申请成功后会获取appid;
  2. 在HBuilder中配置申请的参数(如appid等),提交云端打包生成自定义基座;
  3. 在App项目中用API进行登录,成功后获取到授权标识提交到业务服务器完成登录操作。

该方式需要在项目 mainifest.json 中去开启 OAuth 鉴权模块:

uni02.png

可以看到里面除了前面提到的 一键登录,还包含 苹果登录、微信登录、QQ登录等三方登录平台,因为要涉及开通相关服务,并且当前登录业务鉴权逻辑比较简单(手机号、密码验证),并且app也为上架应用市场,所以这种相对繁琐的方式也就不考虑了。

3. 微信小程序登录

前面两种方式都pass掉了,意味着要获取手机号相关信息在APP中是行不通了的,但是不慌,不是还有微信小程序版嘛,正好产品也包含小程序平台,前段时间做公众号网页开发时也是包含登录授权,所以小程序的授权登录应该也差不多,而且小程序对比APP来说相对便捷(缺点是某些涉及原生插件相关的功能暂时无法使用)。

同样,先去微信官方文档查阅,看到有两种方式可以获取:

uni03.png

下面具体介绍下实现方案:

3-1. 纯前端实现
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber" plain="true">获取手机号</button>

这个 button 里面的一些属性及事件的具体用法说明可以去看文档说明:uniapp button 用法,文档解释的很清楚,写法也是固定的。

这里还需要用到一个加解密插件:WXBizDataCrypt,下载链接如下,

https://res.wx.qq.com/wxdoc/dist/assets/media/aes-sample.eae1f364.zip

可以去下载选择对应的版本,目前有 Java、C++、Node、Python四个版本,我们这里选择Node版本,将 WXBizDataCrypt.js 添加到项目中

完整代码如下:

<!-- testPhone.vue -->
<template>
<view class="wrap">
    <view class="box-container">
        <input v-model="phone" />
        <view class="action-btn">
                <button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber" plain="true">获取手机号</button>
        </view>
    </view>
</view>
</template>

<script>
import WXBizDataCrypt from '@/utils/WXBizDataCrypt.js'

export default {
    data() {
        return {
            phone: "",
            phone_iv: "",
            js_code: "",
            session_key: "",
            phone_encryptedData: null,
        }
    },
    onShow() {
            this.initLogin()
    },
    methods: {
        initLogin() {
            uni.login({
                    provider: 'weixin',
                    success: res => {
                        this.js_code = res.code
                        uni.request({
                                url: 'https://api.weixin.qq.com/sns/jscode2session', // 请求微信服务器
                                method: 'GET',
                                data: {
                                    appid: 'xxxxxxxx', // 微信appid
                                    secret: 'xxxxxxxxxxxxx', // 微信秘钥
                                    js_code: this.js_code,
                                    grant_type: 'authorization_code'
                                },
                                success: (res) => {
                                    console.log('获取信息', res.data);
                                    this.session_key = res.data.session_key
                                }
                        });
                    }
                });
            },
        getPhoneNumber(res) {
            console.log(res, '---手机号回调信息')
            this.phone_encryptedData = res.detail.encryptedData;
            this.phone_iv = res.detail.iv;
            let pc = new WXBizDataCrypt('填写微信appid', this.session_key);
            try {
                let data = pc.decryptData(this.phone_encryptedData, this.phone_iv);
                if (data.phoneNumber !== '') {
                    this.phone = data.phoneNumber;
                }
            } catch (error) {
                console.error('获取失败:', error);
            }
        }
    }
}
</script>

<style lang="less">
.wrap {
    width: 100vw;
    height: 100vh;
    background-color: #F1F2F6;
    display: flex;
    align-items: center;
    justify-content: center;

    .box-container {
        width: 70vw;
        height: 30vh;

        input {
            border: 2rpx solid black;
        }

        .action-btn {
            width: 50%;
            height: 80rpx;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 40rpx auto;
        }
    }
}
</style>

大致流程是:

先通过 uni.login 拿到一个 code,用这 code 作为js_code、appid(微信小程序后台设置中获取)、secret(微信公众号后台获取的密钥)、grant_type(固定值:authorization_code) 去请求 https://api.weixin.qq.com/sns/jscode2session 这个地址,返回结果如下:

{"session_key":"zkJJOfHPYHc\/cVK2kydibg==","openid":"oHXOj5NJMH78yWdVcf6loGOL4cno"}

然后点击按钮调起微信手机号授权页:

999.png

@getphonenumber 事件的回调中获取的信息打印结果如下:

888.png

框选的信息就是我们需要的,是一个加密后的数据。

最后使用 WXBizDataCrypt 对信息进行解密,解密后就是我们需要的手机号信息了。

3-2. 前后端实现

前端代码逻辑改了下:

<script>
export default {
    data() {
            return {
                    phone: "",
            }
    },
    methods: {
        getPhoneNumber(res) {
            console.log(res, '---手机号回调信息')
            // 注:这里的code和前面登录返回的code是不同的
            const { code } = res.detail
            // 根据code去请求后端提供的接口,即可从响应数据中拿到手机号
        }
    }
}
</script>

后端做了哪些事情呢?

首先会去 获取接口调用凭证 ,官方文档描述如下:

777.png

// 参数说明

{
    grant_type: client_credential,      // 固定值
    appid: '',      // 填写微信小程序的appid
    secret: '',     // 填写微信小程序的密钥
}

返回参数为:access_token(接口凭证)expire_in(过期时间,默认为2小时)

然后再去调获取手机号接口(getPhoneNumber),

666.png

参数携带前面返回的 access_token,再加上前端传过来的 code,即可获取到手机号信息。

下面是我用 Postman 对三个接口做了测试验证:

weixin08.png

weixin07.png

weixin06.png

对比两种方式,个人建议还是采用第二种好一点,让相关的业务都在后端去处理,除此之外还有一个原因就是涉及一个安全性相关问题,前面代码中可以看到我们在请求小程序登录接口是将 appid、screct等信息放在请求参数中的,这种极易通过源码拿到,所以存在相关信息泄露问题,事实证明这种方式也是不建议使用的:

555.png

踩坑点

  1. 注意区分登陆时返回的 code 和 button 按钮获取手机号回调返回的 code 是不相同的

  2. @getphonenumber 回调函数的返回信息如果信息为:api scope is not declared in the privacy agreement ,这种是小程序的【隐私保护策略】限制的,排查下你的小程序中用户隐私保护指引设置送是否添加了相关的用户隐私类型(手机号、通讯录、位置信息等)

444.png

以上就是结合项目需求场景对获取手机号的实现做的一个记录!