为什么小程序登录要后端配合
微信小程序的登录流程和传统 Web 完全不同。前端调用 wx.login() 拿到的是临时凭证 code,这个 code 只能用一次,而且必须在服务端换取 openid 和 session_key。所以无论你用 ThinkPHP 还是其他框架,核心逻辑都绕不开服务端。
另外,从 2022 年开始微信调整了用户信息获取策略,wx.getUserProfile 被回收,头像昵称改为「头像昵称填写能力」。手机号获取也升级为 getPhoneNumber 事件配合 code 换取。下面我用 ThinkPHP 6 给出可直接落地的代码。
第一步:wx.login 换取 openid
前端先调用登录接口拿到 code,然后传给后端。后端用 code 去请求微信的 jscode2session 接口。
// app/controller/Auth.php
namespace app\controller;
use think\facade\Db;
use think\facade\Request;
use think\facade\Cache;
class Auth
{
// 小程序 appid 和 secret
protected $appid = 'wx1234567890abcdef';
protected $secret = 'your_app_secret';
// 登录换取 openid
public function login()
{
$code = Request::post('code');
if (empty($code)) {
return json(['code' => 0, 'msg' => 'code 不能为空']);
}
$url = "https://api.weixin.qq.com/sns/jscode2session?appid={$this->appid}&secret={$this->secret}&js_code={$code}&grant_type=authorization_code";
$result = json_decode(file_get_contents($url), true);
if (isset($result['errcode'])) {
return json(['code' => 0, 'msg' => $result['errmsg']]);
}
$openid = $result['openid'];
$sessionKey = $result['session_key'];
// 生成自定义登录态 token
$token = md5($openid . time() . rand(1000, 9999));
// 缓存 session_key 和 openid,有效期 7 天
Cache::set('user_' . $token, [
'openid' => $openid,
'session_key' => $sessionKey
], 3600 * 24 * 7);
// 查询或创建用户
$user = Db::name('user')->where('openid', $openid)->find();
if (!$user) {
$userId = Db::name('user')->insertGetId([
'openid' => $openid,
'create_time' => time()
]);
} else {
$userId = $user['id'];
}
return json([
'code' => 1,
'msg' => '登录成功',
'data' => ['token' => $token, 'user_id' => $userId]
]);
}
}
前端拿到 token 后存入 wx.setStorageSync,后续请求放在 header 里即可。
第二步:获取头像昵称
新版小程序不再返回用户真实头像和昵称,需要用户主动填写。前端使用 <button open-type="chooseAvatar"> 和 <input type="nickname">。
<!-- 前端 wxml -->
<button open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
<image src="{{avatarUrl}}"></image>
</button>
<input type="nickname" bindchange="onNicknameChange" placeholder="请输入昵称" />
用户选择后,前端把头像临时路径和昵称上传到后端。注意头像临时文件需要上传到自己的服务器或云存储。
// 后端保存头像昵称
public function updateProfile()
{
$token = Request::header('token');
$cache = Cache::get('user_' . $token);
if (!$cache) {
return json(['code' => 0, 'msg' => '登录已过期']);
}
$nickname = Request::post('nickname');
$avatar = Request::file('avatar');
$avatarUrl = '';
if ($avatar) {
$saveName = \think\facade\Filesystem::disk('public')->putFile('avatar', $avatar);
$avatarUrl = '/storage/' . $saveName;
}
Db::name('user')->where('openid', $cache['openid'])->update([
'nickname' => $nickname,
'avatar' => $avatarUrl,
'update_time' => time()
]);
return json(['code' => 1, 'msg' => '更新成功', 'avatar' => $avatarUrl]);
}
第三步:获取手机号
手机号获取需要企业主体的小程序,并且要调用 getPhoneNumber 事件拿到 code,再用 code 去请求微信接口换取手机号。
// 前端
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">获取手机号</button>
getPhoneNumber(e) {
if (e.detail.code) {
wx.request({
url: 'https://yourdomain.com/api/getPhone',
method: 'POST',
header: { token: wx.getStorageSync('token') },
data: { code: e.detail.code },
success: res => console.log(res.data)
})
}
}
// 后端换取手机号
public function getPhone()
{
$code = Request::post('code');
if (empty($code)) {
return json(['code' => 0, 'msg' => 'code 不能为空']);
}
$token = Request::header('token');
$cache = Cache::get('user_' . $token);
if (!$cache) {
return json(['code' => 0, 'msg' => '登录已过期']);
}
// 获取 access_token
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={$accessToken}";
$data = json_encode(['code' => $code]);
$options = [
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => $data
]
];
$result = json_decode(file_get_contents($url, false, stream_context_create($options)), true);
if ($result['errcode'] != 0) {
return json(['code' => 0, 'msg' => $result['errmsg']]);
}
$phone = $result['phone_info']['purePhoneNumber'];
Db::name('user')->where('openid', $cache['openid'])->update([
'phone' => $phone
]);
return json(['code' => 1, 'msg' => '获取成功', 'phone' => $phone]);
}
// 获取 access_token 并缓存
protected function getAccessToken()
{
$token = Cache::get('wx_access_token');
if ($token) {
return $token;
}
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appid}&secret={$this->secret}";
$result = json_decode(file_get_contents($url), true);
if (isset($result['access_token'])) {
// access_token 有效期 7200 秒,提前 5 分钟过期
Cache::set('wx_access_token', $result['access_token'], 7200 - 300);
return $result['access_token'];
}
return '';
}
几个容易踩的坑
- access_token 必须全局缓存:微信限制获取频率,如果每次请求都重新获取,很容易触发限制。用 Redis 或文件缓存都行。
- session_key 不要下发给前端:它相当于解密密钥,泄露会有安全风险。放在服务端缓存即可。
- 手机号 code 只能用一次:前端拿到后要立即传给后端,不要重复使用。
- 头像上传要处理临时路径:小程序返回的是临时文件路径,必须上传到自己的服务器或云存储才能持久化。
建议把 appid 和 secret 放到.env文件里,不要硬编码在控制器中。ThinkPHP 6 使用Env::get('wx.appid')读取即可。
以上代码在 ThinkPHP 6 + 微信小程序基础库 2.27 以上版本实测可用。如果你用的是 ThinkPHP 5,把命名空间和请求方式稍作调整即可,核心逻辑完全一致。
评论 (0)
还没有评论,快来抢沙发吧~