uni-app微信小程序获取用户手机号(纯前端)

7,918 阅读7分钟

这篇笔记主要记录,uni-app小程序获取用户手机号。我们的流程是:

  1. 点击获取手机号按钮拿code
  2. 把拿到的code作为入参, 调用微信提供的获取手机号接口, 拿到用户手机号
  3. 因为微信不允许前端直接发起请求拿手机号,我们使用云函数发起请求

小程序文档里,获取手机号分为手机号快速验证组件手机号实时验证组件,2者的区别就是实时的话,我们需要输入验证码完成授权。

image.png

下面截图是微信开发者工具的,弹出的授权界面没有使用其他号码按钮,界面比较凌乱,因为我们调试,把code、access_token, 返回的手机号都界面输出了,直观的看是否获取成功

image.png

这篇笔记以手机号快速验证组件为例,咱们开始吧:)

1. 添加获取手机号按钮

先看下小程序文档的示例

<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>

Page({
  getPhoneNumber (e) {
    console.log(e.detail.code)  // 动态令牌
    console.log(e.detail.errMsg) // 回调信息(成功失败都会返回)
    console.log(e.detail.errno)  // 错误码(失败时返回)
  }
})

咱们用的是uni-app, 改成uni-app形式

<script setup>
const getPhoneNumber = (e) => {
  console.log(e.detail.code)  // 动态令牌
  console.log(e.detail.errMsg) // 回调信息(成功失败都会返回)
  console.log(e.detail.errno)  // 错误码(失败时返回)
}
</script>
<template>
    <button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
      获取手机号
    </button>
</template>

就可以弹出授权手机号界面了

这里没弹出手机号授权界面的,首先确保HBuilderX绑定了小程序AppID, 然后看下小程序是不是完成了认证的非个人账号,因为账号资质问题,我卡了一个月😭😭😭

点击手机号快速验证组件按钮,授权同意后,就成功发起bindgetphonenumber事件

回调返回的完整json,我们要的code在detail里,

{
  "type": "getphonenumber",
  "timeStamp": 31816,
  "target": {
    "id": "",
    "offsetLeft": 10,
    "offsetTop": 327,
    "dataset": {},
    "errMsg": "getPhoneNumber:ok",
    "encryptedData": "+B/RoWALhNY+y8So62Q+rMZOiqeBGAW0stY891j0cMtSxvjjlkj/LX1lOrth8yV2JIqrPhQQpBlrTRZI9lRh0TUI0GPpK293vXULnGKT9V8pPzNDfog35t+s6tOS1bd/eHOOvbTJQJJiIaWHnkcLMSXQFwK2zHNy6jcWypr5NF1OhCHKKDLzkmvBhrw4pWPlqOD19dr92Nd9l2QuxttBgQ==",
    "iv": "aFD1Tb2OC6D/xkLbnRJHOQ==",
    "code": "33ef7a4d2c00c40d9188a892c6a20635c9fcdae3835d74bb0390f13cb07ce7f1"
  },
  "currentTarget": {
    "id": "",
    "offsetLeft": 10,
    "offsetTop": 327,
    "dataset": {}
  },
  "mark": {},
  "detail": {
    "errMsg": "getPhoneNumber:ok",
    "encryptedData": "+B/RoWALhNY+y8So62Q+rMZOiqeBGAW0stY891j0cMtSxvjjlkj/LX1lOrth8yV2JIqrPhQQpBlrTRZI9lRh0TUI0GPpK293vXULnGKT9V8pPzNDfog35t+s6tOS1bd/eHOOvbTJQJJiIaWHnkcLMSXQFwK2zHNy6jcWypr5NF1OhCHKKDLzkmvBhrw4pWPlqOD19dr92Nd9l2QuxttBgQ==",
    "iv": "aFD1Tb2OC6D/xkLbnRJHOQ==",
    "code": "33ef7a4d2c00c40d9188a892c6a20635c9fcdae3835d74bb0390f13cb07ce7f1"
  },
  "mut": false,
  "_userTap": false,
  "__evName": "getphonenumber"
}

拿到了code我们就想去获取调用手机号接口,看下文档, 请求参数需要access_token, code

image.png

那我们就去拿access_token

2. 获取接口调用凭证access_token

看获取access_token文档, 我们需要

  1. appid (这个直接去小程序后台,开发管理 > 开发设置里复制)
  2. secret (这个直接去小程序后台,开发管理 > 开发设置里复制)
  3. grant_type填写client_credential

image.png

const appid= 'xxxxxxxx'
const secret = 'xxxxxxxxxxxxxxx'
const getAccessToken = async () => {
  let result = await uni.request({
    url: `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`,
    method: "GET",
  });
  token.value = result;
};
getAccessToken();

postman请求截图

image.png

返回的包括access_token对象

{
  "data": {
    "access_token": "86_96nh7Bgl6XVg-FArejyp2XSUefcOmq8y5uS0rmq5kGw8AK713oWMQE2XrbBpkVIO1eCxev4OoPovl9xQB-WvwMNYmJ0VhHklRkJTErts8Q9koA7H2wIFFWcXOcwLZUeAJAXLG",
    "expires_in": 7200
  },
  "header": {
    "Connection": "keep-alive",
    "Content-Type": "application/json; encoding=utf-8",
    "Date": "Wed, 27 Nov 2024 03:52:03 GMT",
    "Content-Length": "173"
  },
  "statusCode": 200,
  "cookies": [],
  "accelerateType": "none",
  "errMsg": "request:ok"
}

3. 调用获取手机号接口

获取手机号码接口需要

  1. code
  2. access_token或者authorizer_access_token

image.png

根据文档, 接口地址如下,

POST https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN 

post请求,传2个参数,经过前2步,我们有了code, 也有access_token, 就可以调用获取手机号接口了, 咱们写一下请求方法

  const response = await uni.request({
    url: `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${access_token}`, 
    method: "POST",
    data: {
      code: val.detail.code,
    },
  });

返回的response对象, 我们要的phoneNumber在phone_info里,我把自己的手机号码加了****

{
  "data": {
    "errcode": 0,
    "errmsg": "ok",
    "phone_info": {
      "phoneNumber": "151****3200",
      "purePhoneNumber": "151****3200",
      "countryCode": "86",
      "watermark": {
        "timestamp": 1732670685,
        "appid": "wxdc6f7096d4b98844"
      }
    }
  },
  "header": {
    "Connection": "keep-alive",
    "Content-Type": "application/json; charset=UTF-8",
    "Content-Length": "187"
  },
  "statusCode": 200,
  "cookies": [],
  "accelerateType": "none",
  "errMsg": "request:ok"
}
总结: 获取手机号完整代码
<script setup>
const getPhoneNumber = async (val) => {
  const appid= 'xxxxxxxx'
  const secret = 'xxxxxxxxxxxxxxx'
  // 获取access_token
  let {
    data: { access_token },
  } = await uni.request({
    url: `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`,
    method: "GET",
  });
  // 获取手机号
  const response = await uni.request({
    url: `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${access_token}`, 
    method: "POST",
    data: {
      code: val.detail.code,
    },
  });
  let phoneNumber = response.data?.phone_info?.phoneNumber
  console.log('手机号', phoneNumber)
};
</script>
<template>
    <button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
      获取手机号
    </button>
</template>

代码量很少吧, 其实就是调用了2个接口,先拿access_token, 再拿手机号。

真机模拟出现问题

真机模拟出现问题,不会发请求,想着以为是没配置域名白名单,配了提示为保障账号安全不可使用此域名地址,请修改 image.png 原因如下

如填写了“api.weixin.qq.com”会出现上述错误提示。出于安全考虑,为避免开发者将AppSecret放置在小程序的前端代码内,平台禁止设置此域名。

小程序的开发者密码(AppSecret)是一个非常重要的字段,使用该密码可以调用小程序的所有后台接口。请不要将该字段放置在微信小程序的前端代码中,因为微信手机客户端容易被反编译并轻松获得Appsecret,造成重大的安全威胁。开发者应将Appsecret保存到后台服务器中,通过服务器使用Appsecert获取Accesstoken。微信公众平台小程序后台的服务器地址设置也将禁止将“api.weixin.qq.com”域名的配置,所有对于“api.weixin.qq.com”域名下的接口请求请全部通过后台服务器发起,请勿直接通过小程序的前端代码发起。

就是说咱们得从后端发起请求,来到前端痛点了,会一点nodejs, 还要配置服务器,部署(感觉不好操作),还好最近学了云函数,通过 云函数云对象 发起请求属于后端服务器操作。

通过云函数发起请求

云函数是运行在云端的 JavaScript 代码,是基于 Node.js 的扩展, 看下云函数的文档

新建一个请求手机号的云函数getPhoneNumber

在云函数点击右键,选择新建云函数/云对象,取名getPhoneNumber

image.png 建好后多了一个文件夹,里面有一个index.js文件和package.json, 上面截图多个node_modules和pnpm-lock.yaml, 因为我安装了依赖axios

我们在index.js写下面的代码,就是先调用获取access_token,然后获取手机号,然后把结果返回

"use strict";
const axios = require("axios");
exports.main = async (event, context) => {
  //event为客户端上传的参数
  const { code } = event; // 从前端传来的code
  const appid = "xxxxxxxxx"; // 替换为你的小程序 AppID
  const secret = "xxxxxxxxx"; // 替换为你的 AppSecret

  try {
    // 1. 获取 access_token
    const tokenRes = await axios.get(
      `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`
    );
    const access_token = tokenRes.data.access_token;

    if (!access_token) {
      return { success: false, msg: "Failed to fetch access_token" };
    }

    // 2. 获取用户手机号
    const phoneRes = await axios.post(
      `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${access_token}`,
      { code }
    );

    if (phoneRes.data.errcode === 0 && phoneRes.data.phone_info) {
      return {
        success: true,
        phoneNumber: phoneRes.data.phone_info.phoneNumber,
      };
    } else {
      return { success: false, msg: phoneRes.data.errmsg || "Unknown error" };
    }
  } catch (error) {
    return { success: false, msg: "Internal server error" };
  }
};

我们在登录页调用这个云函数,

const getPhoneNumber = async (val) => {
  const { result } = await uniCloud.callFunction({
    name: "getPhoneNumber", // 云函数名称
    data: {
      code: val.detail.code, // 前端获取到的用户授权 code
    },
  });
  console.log("result---", result);

  if (result.success) {
    const mobile = result.phoneNumber;
    // 将手机号存储到数据库和本地缓存
    db.collection("users").add({ mobile });
    uni.setStorageSync("userInfo", { mobile });

    uni.showToast({ title: "授权成功", icon: "success" });
    uni.switchTab({ url: "/pages/me/me" });
  } else {
    uni.showToast({ title: "获取手机号失败", icon: "none" });
    console.error(result.msg);
  }
};

如果我们本地调试用的是云端云函数,记得写完云函数就上传到云端

通过云函数日志查看是否调用成功

因为在云函数里console.log, 前端开发中工具控制台是不会输出的,登录uni-cloud控制台,点击查看日志按钮,

image.png

云函数日志提示axios is not defined image.png

云函数日志调用成功 image.png

再次真机模拟,就可以了,请求发出去,拿到手机号了

image.png