这篇笔记主要记录,uni-app小程序获取用户手机号。我们的流程是:
- 点击获取手机号按钮拿code
- 把拿到的code作为入参, 调用微信提供的获取手机号接口, 拿到用户手机号
- 因为微信不允许前端直接发起请求拿手机号,我们使用云函数发起请求
小程序文档里,获取手机号分为手机号快速验证组件 和 手机号实时验证组件,2者的区别就是实时的话,我们需要输入验证码完成授权。
下面截图是微信开发者工具的,弹出的授权界面没有使用其他号码按钮,界面比较凌乱,因为我们调试,把code、access_token, 返回的手机号都界面输出了,直观的看是否获取成功
这篇笔记以手机号快速验证组件为例,咱们开始吧:)
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
那我们就去拿access_token
2. 获取接口调用凭证access_token
看获取access_token文档, 我们需要
- appid (这个直接去小程序后台,开发管理 > 开发设置里复制)
- secret (这个直接去小程序后台,开发管理 > 开发设置里复制)
- grant_type填写client_credential
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请求截图
返回的包括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. 调用获取手机号接口
获取手机号码接口需要
根据文档, 接口地址如下,
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, 再拿手机号。
真机模拟出现问题
真机模拟出现问题,不会发请求,想着以为是没配置域名白名单,配了提示为保障账号安全不可使用此域名地址,请修改
原因如下
如填写了“api.weixin.qq.com”会出现上述错误提示。出于安全考虑,为避免开发者将AppSecret放置在小程序的前端代码内,平台禁止设置此域名。
小程序的开发者密码(AppSecret)是一个非常重要的字段,使用该密码可以调用小程序的所有后台接口。请不要将该字段放置在微信小程序的前端代码中,因为微信手机客户端容易被反编译并轻松获得Appsecret,造成重大的安全威胁。开发者应将Appsecret保存到后台服务器中,通过服务器使用Appsecert获取Accesstoken。微信公众平台小程序后台的服务器地址设置也将禁止将“api.weixin.qq.com”域名的配置,所有对于“api.weixin.qq.com”域名下的接口请求请全部通过后台服务器发起,请勿直接通过小程序的前端代码发起。
就是说咱们得从后端发起请求,来到前端痛点了,会一点nodejs, 还要配置服务器,部署(感觉不好操作),还好最近学了云函数,通过 云函数 或 云对象 发起请求属于后端服务器操作。
通过云函数发起请求
云函数是运行在云端的 JavaScript 代码,是基于 Node.js 的扩展, 看下云函数的文档。
新建一个请求手机号的云函数getPhoneNumber
在云函数点击右键,选择新建云函数/云对象,取名getPhoneNumber
建好后多了一个文件夹,里面有一个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控制台,点击查看日志按钮,
云函数日志提示axios is not defined
云函数日志调用成功
再次真机模拟,就可以了,请求发出去,拿到手机号了