uni-app 登录授权全流程实战:微信登录、手机号验证码与头像昵称填写

uni-app 登录授权全流程实战:微信登录、手机号验证码与头像昵称填写

admin
2026-07-27 / 0 评论 / 6 阅读

微信登录体系这几年持续收紧:getUserProfile 收回了、头像昵称获取改填写了、手机号要企业认证了。网上大量教程已经过时。本文基于当前有效的接口体系,实现一套完整的登录授权方案,并覆盖 H5 与 App 端的差异。

一、当前微信登录接口的正确认知

先纠正几个过时认知:

接口现状
wx.getUserInfo已回收,返回匿名数据
wx.getUserProfile已于 2022 年后收紧,新版本基本不可用
头像昵称官方推荐用「头像昵称填写能力」(open-type 按钮 + input type=nickname)
getPhoneNumber可用,但需要企业主体小程序,且按次收费
wx.login 换 openid正常可用,这是静默登录的基础

结论:openid 换 token 做静默登录 + 头像昵称用户主动填 + 手机号授权按钮,是当前唯一合规的组合。

二、静默登录:openid 链路

用户打开小程序不需要任何操作就完成注册/登录:

前端 wx.login 获取 code
    → POST /auth/silent-login { code }
    → 后端 code2session 换 openid + session_key
    → 后端按 openid 查/建用户 → 签发 token
    → 前端存 token,静默登录完成
// stores/user.js
async silentLogin() {
  const [err, res] = await uni.login({ provider: 'weixin' })
  if (err) return

  const data = await request.post('/auth/silent-login', { code: res.code })
  this.token = data.accessToken
  this.isNewUser = data.isNewUser // 后端标记:openid 没绑定过手机号
}

后端 code2session 注意点:code 只能用一次、5 分钟有效session_key 千万不能下发到前端(安全隐患,微信明确禁止);unionid 需要绑定开放平台才能拿到,多端账号打通靠它。

三、头像昵称填写:官方推荐方案

微信现在要求用户"主动填写"头像和昵称,配套了两个专用 UI 能力:

头像:button open-type="chooseAvatar"

<template>
  <button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
    <image class="avatar" :src="avatarUrl || '/static/default-avatar.png'" mode="aspectFill" />
    <text class="avatar-tip">点击选择头像</text>
  </button>
</template>

<script setup>
import { ref } from 'vue'

const avatarUrl = ref('')

function onChooseAvatar(e) {
  // 临时文件路径,需要上传到自己的服务器换取永久 URL
  avatarUrl.value = e.detail.avatarUrl
}
</script>

关键e.detail.avatarUrl 是临时路径,小程序重启即失效,必须立刻上传:

async function onChooseAvatar(e) {
  const tempPath = e.detail.avatarUrl
  uni.showLoading({ title: '上传中' })
  try {
    const url = await uploadFile(tempPath) // 走自己的上传接口
    avatarUrl.value = url
  } finally {
    uni.hideLoading()
  }
}

昵称:input type="nickname"

<template>
  <view class="form-item">
    <text class="label">昵称</text>
    <input
      v-model="nickname"
      type="nickname"
      placeholder="请输入昵称"
      @blur="onNicknameBlur"
    />
  </view>
</template>

<script setup>
import { ref } from 'vue'
const nickname = ref('')

function onNicknameBlur(e) {
  // type=nickname 的 input 在部分机型 v-model 同步不及时,blur 时兜底读取
  nickname.value = e.detail.value
}
</script>

type="nickname" 会唤起微信官方的昵称快捷填写键盘(自动带入微信昵称),这是目前唯一合规的获取昵称方式。

提交完善资料

async function saveProfile() {
  if (!avatarUrl.value) return uni.showToast({ title: '请选择头像', icon: 'none' })
  if (!nickname.value.trim()) return uni.showToast({ title: '请填写昵称', icon: 'none' })

  await request.post('/me/profile', {
    avatar: avatarUrl.value,
    nickname: nickname.value
  })

  userStore.fetchProfile()
  uni.showToast({ title: '保存成功' })
  setTimeout(() => uni.navigateBack(), 800)
}

四、手机号授权:企业认证方案

getPhoneNumber 需要 button 触发,且小程序必须完成微信认证(企业主体)

<template>
  <button
    class="phone-btn"
    open-type="getPhoneNumber"
    @getphonenumber="onGetPhone"
  >
    授权手机号登录
  </button>
</template>

<script setup>
async function onGetPhone(e) {
  const detail = e.detail

  // 用户拒绝授权
  if (!detail.code) {
    return uni.showToast({ title: '您取消了授权', icon: 'none' })
  }

  // 新版接口:detail.code 交给后端,后端调 getuserphonenumber 换手机号
  const data = await request.post('/auth/bind-phone', { code: detail.code })

  userStore.userInfo = data.user
  uni.showToast({ title: '登录成功' })
}
</script>

当前流程(2023 之后的版本):

用户点击授权按钮
    → e.detail.code(动态令牌)
    → POST /auth/bind-phone { code }
    → 后端用 code + access_token 调微信接口换真实手机号
    → 绑定用户,返回更新后的用户信息

注意事项:

  1. 个人主体小程序用不了这个能力,认证费用 300 元/年,手机号验证按次计费(约 0.03 元/次)
  2. 旧版 encryptedData + iv 解密方案还能用但不推荐,code 方案更安全且免维护密钥
  3. 计费压力大的场景可以改做短信验证码登录(自建),绕开微信计费

短信验证码登录(自建方案)

<template>
  <view class="sms-login">
    <view class="input-row">
      <input v-model="phone" type="number" maxlength="11" placeholder="手机号" />
    </view>
    <view class="input-row">
      <input v-model="smsCode" type="number" maxlength="6" placeholder="验证码" />
      <button class="sms-btn" :disabled="countdown > 0" @click="sendSms">
        {{ countdown > 0 ? `${countdown}s后重试` : '获取验证码' }}
      </button>
    </view>
    <button class="login-btn" @click="loginBySms">登录</button>
  </view>
</template>

<script setup>
import { ref, onUnmounted } from 'vue'

const phone = ref('')
const smsCode = ref('')
const countdown = ref(0)
let timer = null

async function sendSms() {
  if (!/^1[3-9]\d{9}$/.test(phone.value)) {
    return uni.showToast({ title: '手机号格式错误', icon: 'none' })
  }

  await request.post('/auth/sms/send', { phone: phone.value })
  countdown.value = 60
  timer = setInterval(() => {
    if (--countdown.value <= 0) clearInterval(timer)
  }, 1000)
}

async function loginBySms() {
  const data = await request.post('/auth/sms/login', {
    phone: phone.value,
    code: smsCode.value
  })
  userStore.token = data.accessToken
  userStore.userInfo = data.user
  uni.reLaunch({ url: '/pages/index/index' })
}

onUnmounted(() => timer && clearInterval(timer))
</script>

后端要点:验证码 5 分钟有效、同一手机号 60 秒内不可重发、验证失败 5 次锁定、按手机号 + IP 双维度限流防刷。

五、多端登录差异

// utils/auth.js —— 收敛各端登录入口
export async function doLogin() {
  // #ifdef MP-WEIXIN
  const [err, res] = await uni.login({ provider: 'weixin' })
  return request.post('/auth/wx-login', { code: res.code })
  // #endif

  // #ifdef APP-PLUS
  // App 端:一键登录(运营商授权)
  const [loginErr, loginRes] = await uni.login({ provider: 'univerify' })
  return request.post('/auth/univerify-login', {
    accessToken: loginRes.authResult.access_token,
    openid: loginRes.authResult.openid
  })
  // #endif

  // #ifdef H5
  // H5 端:微信公众号网页授权
  const appId = 'wx_xxx'
  const redirect = encodeURIComponent(location.href)
  location.href =
    `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}` +
    `&redirect_uri=${redirect}&response_type=code&scope=snsapi_userinfo#wechat_redirect`
  // #endif
}

H5 回调页解析 code 换 token:

// H5 授权回调页面
onLoad() {
  const code = new URLSearchParams(location.search).get('code')
  if (code) {
    const data = await request.post('/auth/h5-wx-login', { code })
    userStore.token = data.accessToken
  }
}

六、登录拦截的优雅实现

全局拦截而非每页手写判断。方案是封装路由跳转 + 页面 meta 声明:

// pages.json 页面需要登录的加 custom 字段(或维护一个白名单数组)
const LOGIN_REQUIRED = ['pages/cart/cart', 'pages/order/list']

// 重写跳转方法统一拦截
const originalNavigateTo = uni.navigateTo
uni.navigateTo = function(options) {
  const path = options.url.split('?')[0].replace(/^\//, '')
  const userStore = useUserStore()

  if (LOGIN_REQUIRED.includes(path) && !userStore.isLoggedIn) {
    return originalNavigateTo({
      url: `/pages/login/login?redirect=${encodeURIComponent(options.url)}`
    })
  }
  return originalNavigateTo(options)
}

登录成功后回跳:

async function handleLoginSuccess() {
  const redirect = decodeURIComponent(
    new URLSearchParams(location.search).get('redirect') ||
    getCurrentPagesArgs('redirect') || ''
  )
  uni.reLaunch({ url: redirect || '/pages/index/index' })
}

总结

  • 静默登录靠 wx.login + code2session,session_key 留在服务端
  • 头像用 open-type="chooseAvatar",昵称用 type="nickname",临时文件必须立即上传
  • 手机号授权需要企业认证 + 计费,自建短信验证码是省钱替代
  • 登录入口用条件编译分端收敛,重写 navigateTo 做全局登录拦截
  • 常见过时方案自查:getUserProfile、encryptedData 解密、无企业认证却调 getPhoneNumber

登录授权是合规重灾区,本文方案基于当前有效接口,建议每半年对照微信官方文档核对一次。

0

评论 (0)

取消
0:00