专注小程序 / APP 开发,用代码改变生活

获取方案
全国分站 主站首页 北京分站 石家庄分站 唐山分站 保定分站 廊坊分站 沧州分站 郑州分站

ThinkPHP实战:小程序登录与手机号获取

开发笔记 2026-10-02 2 阅读 0 点赞 原创

为什么小程序登录要后端配合

微信小程序的登录流程和传统 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,把命名空间和请求方式稍作调整即可,核心逻辑完全一致。

CRM系统App开发核心功能全解析ThinkPHP开发小程序签到功能实战

评论 (0)

还没有评论,快来抢沙发吧~

好想法,值得被认真交付

从小程序、APP 到全栈网站,一站式把想法变成可落地的产品

微信咨询

微信扫码,直接沟通需求