微信小程序 静默登录 + 授权登录 双模式配合的设计方案

微信小程序双登录模式完整实现指南

一、概述

本项目采用静默登录 + 授权登录双模式配合的设计方案,实现老用户无感登录、新用户授权注册的完整用户体验。

核心设计目的

登录方式 适用场景 用户体验 目的
静默登录 老用户打开小程序 无感知自动登录 提升老用户体验,减少操作步骤
授权登录 新用户首次使用 需点击授权按钮 获取用户信息完成注册

二、静默登录流程

2.1 触发时机

静默登录不是App.vueonLaunch 中直接触发的,而是通过请求拦截器间接触发的。具体触发链路如下:

复制代码
任意 API 请求返回 code = -1(token 过期/未登录)
    ↓
响应拦截器 (utils/request.js) 匹配 APICodeEnum[-1] = 'redirect'
    ↓
调用 events.redirect() → toLogin()
    ↓
toLogin() → _toLogin() → mnpLogin()(小程序环境)

此外,路由守卫也会在用户访问需要登录的页面时,跳转到登录页:

复制代码
用户访问 meta.auth = true 的页面
    ↓
router.beforeEach 检测无 token
    ↓
next('/pages/login/login') 跳转登录页

2.2 触发机制详解

触发点 1:请求拦截器(主要触发方式)

当任意 API 请求返回 code = -1 时,表示 token 无效或用户未登录,响应拦截器会自动触发静默登录:

javascript 复制代码
// utils/enum.js - 接口返回码枚举
export const APICodeEnum = {
    1: 'success',      // 成功
    0: 'fail',         // 失败
    '-1': 'redirect',  // 重定向(token失效,需要重新登录)
    1020: 'closeShop', // 商城关闭
    10: 'tips'         // 提示
}
javascript 复制代码
// utils/request.js - 响应拦截器

import { APICodeEnum } from './enum'
import { toLogin } from './login'

const events = {
    success({ data }) {
        return Promise.resolve(data)
    },
    fail({ msg }) {
        return Promise.reject(msg)
    },
    // 当 code = -1 时触发
    redirect({ msg }) {
        // #ifdef H5
        if (store.getters.appConfig.h5_status) {
            toLogin()  // 触发静默登录
        }
        // #endif
        // #ifdef MP-WEIXIN
        if (store.getters.appConfig.mnp_status) {
            toLogin()  // 触发静默登录
        }
        // #endif
        store.commit('logout')  // 清除本地登录状态
        return Promise.reject(msg)
    },
    closeShop({ msg }) { /* ... */ },
    tips({ code, msg }) { /* ... */ }
}

// 响应拦截器中根据 code 分发到对应的 events 方法
service.interceptors.response.use((response) => {
    const { msg, code, data, show } = response.data
    if (show && msg && code !== 10) {
        toast({ title: msg })
    }
    return events[APICodeEnum[code]](response.data)  // code=-1 → events.redirect()
})
触发点 2:路由守卫(辅助触发方式)

当用户访问需要登录的页面(meta.auth = true)时,路由守卫会拦截并跳转到登录页:

javascript 复制代码
// router.js - 路由守卫

const whiteList = ['register', 'login', 'forget_pwd']

router.beforeEach((to, from, next) => {
    // 保存登录前的路径(排除白名单页面)
    const index = whiteList.findIndex((item) => from.path.includes(item))
    if (index == -1 && !store.getters.token) {
        Cache.set(BACK_URL, from.fullPath)
    }
    // 需要登录的页面 + 无 token → 跳转登录页
    if (to.meta.auth && !store.getters.token &&
        to.path !== '/bundle/pages/business_suspended/business_suspended') {
        next('/pages/login/login')
        return
    } else {
        next()
    }
})
toLogin() 中转函数

toLogin 是静默登录的入口函数,在小程序环境中调用 mnpLogin()

javascript 复制代码
// utils/login.js

export const toLogin = trottle(_toLogin, 2000)  // 节流,2秒内只触发一次

function _toLogin() {
    // #ifdef MP
    mnpLogin()          // 小程序环境:触发静默登录
    // #endif
    // #ifndef MP
    const { currentRoute } = router
    if (currentRoute.meta.auth) {
        router.push("/pages/login/login")  // 非小程序环境:跳转登录页
    }
    // #endif
}

2.3 完整流程图

复制代码
用户打开小程序,触发任意 API 请求
    ↓
后端返回 code = -1(token无效/未登录)
    ↓
响应拦截器匹配 APICodeEnum[-1] = 'redirect'
    ↓
调用 events.redirect() → toLogin()
    ↓
toLogin() → _toLogin() → mnpLogin()(小程序环境)
    ↓
获取配置 (coerce_mobile, mnp_auto_wechat_auth)
    ↓
检查是否开启自动授权 (mnp_auto_wechat_auth)
    ├─ 未开启 → 终止流程
    └─ 已开启 → 继续
    ↓
调用 uni.login() 获取 code
    ↓
请求后端接口 login/silentLogin
    ↓
后端使用 code 换取 openid
    ↓
查询数据库:该 openid 是否存在?
    ├─ 存在(老用户)→ 返回 is_new_user: false, token
    └─ 不存在(新用户)→ 返回 is_new_user: true
    ↓
前端判断 !loginData.is_new_user
    ├─ true(老用户)→ 保存 token,刷新页面 ✅
    └─ false(新用户)→ 不执行登录,保持未登录状态 ❌
    ↓
(新用户后续操作)
    ├─ 访问 meta.auth=true 的页面 → 路由守卫拦截 → 跳转登录页
    └─ 再次触发 API 请求 → code=-1 → toLogin() → mnpLogin() → 仍然不登录

2.3 代码实现(按流程顺序)

步骤 1:获取登录凭证 code
javascript 复制代码
// utils/login.js

export function getCode() {
  return new Promise((resolve, reject) => {
    uni.login({
      success(res) {
        resolve(res.code);
      },
      fail(res) {
        reject(res);
      },
    });
  });
}
步骤 2:定义静默登录 API 接口
javascript 复制代码
// api/app.js

// 微信小程序静默登录
export const apiSilentLogin = (params) => request.post('login/silentLogin', params)
步骤 3:静默登录核心逻辑
javascript 复制代码
// utils/login.js

export async function mnpLogin() {
  // 1. 获取系统配置
  const { coerce_mobile, mnp_auto_wechat_auth, toutiao_auto_auth } =
    store.getters.appConfig;

  // 2. 检查是否开启自动授权
  //#ifdef  MP-WEIXIN
  if (!mnp_auto_wechat_auth) return;
  //#endif

  //#ifdef  MP-TOUTIAO
  if (!toutiao_auto_auth) return;
  //#endif

  // 3. 获取 code
  const code = await getCode();

  // 4. 调用静默登录接口
  //#ifdef  MP-WEIXIN
  const loginData = await apiSilentLogin({
    code,
  });
  //#endif

  //#ifdef  MP-TOUTIAO
  const loginData = await apiToutiaoSilentLogin({
    code,
  });
  //#endif

  // 5. 获取当前页面信息
  const { options, onLoad, onShow, route } = currentPage();

  // 6. 如果需要强制绑定手机号且用户未绑定,则终止
  if (coerce_mobile && !loginData.mobile) {
    return;
  }

  // 7. 【关键判断】只有老用户才完成登录
  if (loginData.token && !loginData.is_new_user) {
    store.commit("login", loginData);     // 保存 token 到 store
    store.dispatch("getUser");            // 获取用户信息
    store.dispatch("getCartNum");         // 获取购物车数量
    onLoad && onLoad(options);            // 刷新当前页面
    onShow && onShow();
  }
  // 新用户不会执行以上代码,保持未登录状态
}

2.4 后端返回数据结构

javascript 复制代码
// 老用户返回
{
  token: "eyJ0eXAiOiJKV1Qi...",
  is_new_user: false,
  mobile: "13800138000",  // 可能为空
  // 其他用户信息...
}

// 新用户返回
{
  token: "eyJ0eXAiOiJKV1Qi...",
  is_new_user: true,
  mobile: null,
  // 其他用户信息...
}

三、授权登录流程

3.1 触发时机

用户主动点击登录页面的"用户一键登录"按钮。

3.2 完整流程图

复制代码
用户点击"用户一键登录"
    ↓
检查是否同意服务协议和隐私协议
    ├─ 未同意 → 弹出协议提示框
    └─ 已同意 → 继续
    ↓
调用 uni.getUserProfile() 获取用户信息
    ↓
显示 loading "登录中..."
    ↓
调用 uni.login() 获取 code
    ↓
请求后端接口 login/authLogin
    ↓
后端处理注册/登录逻辑
    ↓
返回 is_new_user 标识
    ├─ true(新用户)→ 弹出 mplogin-popup 完善信息
    └─ false(老用户)→ 直接完成登录
    ↓
弹出完善信息弹窗(仅新用户)
    ↓
用户填写信息并提交
    ↓
调用 login/updateUser 更新用户信息
    ↓
调用 loginHandle() 完成登录
    ↓
跳转回原页面或首页

3.3 代码实现(按流程顺序)

步骤 1:定义授权登录 API 接口
javascript 复制代码
// api/app.js

// 微信小程序授权登录
export const apiAuthLogin = (params) => request.post('login/authLogin', params)

// 更新小程序用户信息
export const apiUpdateUser = (params, token) => {
    return request.post('login/updateUser', params, { headers: { token } })
}
步骤 2:登录页面 - 一键登录按钮
vue 复制代码
<!-- pages/login/login.vue -->

<template>
  <!-- #ifdef MP-WEIXIN -->
  <button
    class="login-btn white login-btn-user"
    v-if="isMnpWxAuth"
    @tap="mnpLogin"
  >
    用户一键登录
  </button>
  <!-- #endif -->
</template>
步骤 3:授权登录核心逻辑
javascript 复制代码
// pages/login/login.vue

methods: {
  // 小程序微信登录
  async mnpLogin() {
    // 1. 检查是否同意协议
    if (!this.isAgree) {
      this.showModel = true;
      return;
    }

    // 2. 获取用户信息(头像、昵称等)
    const {
      userInfo: { avatarUrl, nickName, gender }
    } = await getUserProfile();

    // 3. 显示 loading
    uni.showLoading({
      title: '登录中...',
      mask: true
    });

    // 4. 获取 code
    const wxCode = await getCode();

    // 5. 调用授权登录接口
    const data = await apiAuthLogin({
      code: wxCode,
      nickname: nickName,
      headimgurl: avatarUrl
    });

    // 6. 判断是否为新用户
    if (data.is_new_user) {
      // 新用户:显示完善信息弹窗
      uni.hideLoading();
      this.showLoginPop = true;
      this.loginData = data;
    } else {
      // 老用户:直接完成登录
      this.loginHandle(data);
    }
  },
}
步骤 4:登录结果处理
javascript 复制代码
// pages/login/login.vue

methods: {
  // 登录结果处理
  async loginHandle(data) {
    // 1. 保存 token 到 store
    this.login(data);
    
    // 2. 获取用户信息
    this.getUser();
    
    // 3. 更新购物车
    this.$store.dispatch('getCartNum');
    
    // 4. 隐藏 loading
    uni.hideLoading();
    
    // 5. 如果需要绑定手机号
    if (this.isBindMobile && !data.mobile) {
      return this.$Router.replace('/pages/bind_mobile/bind_mobile');
    }
    
    // 6. 返回上一页或首页
    this.goBack();
  },

  goBack() {
    if (getCurrentPages().length > 1) {
      this.$Router.back(1, {
        success: () => {
          const { onLoad, options } = currentPage();
          onLoad && onLoad(options);  // 刷新上一页
        }
      });
    } else if (Cache.get(BACK_URL)) {
      this.$Router.replace(Cache.get(BACK_URL));
    } else {
      this.$Router.replaceAll('/pages/index/index');
    }
    Cache.remove(BACK_URL);
  }
}
步骤 5:完善用户信息弹窗(仅新用户)
vue 复制代码
<!-- pages/login/login.vue -->

<!-- #ifdef MP-WEIXIN -->
<mplogin-popup
  v-model="showLoginPop"
  :logo="appConfig.logo"
  :title="appConfig.shop_name"
  :login-data="loginData"
  @close="closePopup"
  @update="handleSubmitInfo"
/>
<!-- #endif -->
javascript 复制代码
// pages/login/login.vue

methods: {
  // 提交完善的信息
  async handleSubmitInfo(e) {
    const loginData = this.loginData;
    const res = await apiUpdateUser(e, loginData.token);
    this.loginHandle(loginData);
  },

  closePopup() {
    this.showLoginPop = false;
  }
}

四、两种登录方式的配合机制

4.1 配合流程图

复制代码
用户打开小程序,触发任意 API 请求
    ↓
后端返回 code = -1(未登录/token无效)
    ↓
响应拦截器 → toLogin() → mnpLogin()(静默登录)
    ↓
后端判断新老用户
    ├─ 老用户 → 自动登录成功 ✅
    └─ 新用户 → 不执行登录,保持未登录状态 ❌
    ↓
(如果是新用户)后续两种触发方式:
    ├─ 方式1:再次触发 API 请求 → code=-1 → toLogin() → mnpLogin() → 仍不登录
    └─ 方式2:访问 meta.auth=true 的页面 → 路由守卫拦截 → 跳转登录页
    ↓
跳转到登录页 /pages/login/login
    ↓
用户点击"用户一键登录"
    ↓
触发授权登录 apiAuthLogin()
    ↓
获取用户信息,完成注册
    ↓
(如果是新用户)弹出完善信息弹窗
    ↓
用户提交信息,更新用户资料
    ↓
登录成功,跳转回原页面

4.2 关键配合点

1. 静默登录的拦截机制
javascript 复制代码
// 静默登录中,只有老用户才会执行登录逻辑
if (loginData.token && !loginData.is_new_user) {
  store.commit("login", loginData);  // 仅老用户执行
  // ...
}

目的:防止新用户自动登录,确保新用户必须经过授权流程。

2. 授权登录的新用户识别
javascript 复制代码
// 授权登录中,根据 is_new_user 决定后续流程
if (data.is_new_user) {
  // 新用户:显示完善信息弹窗
  this.showLoginPop = true;
  this.loginData = data;
} else {
  // 老用户:直接完成登录
  this.loginHandle(data);
}

目的:新用户需要完善信息,老用户直接登录。

4.3 为什么需要两种登录方式?

问题 仅用静默登录 仅用授权登录 双模式配合
老用户体验 ✅ 无感知 ❌ 每次都要点击 ✅ 无感知
新用户注册 ❌ 无法获取头像昵称 ✅ 可获取 ✅ 可获取
隐私合规 ❌ 新用户未授权就注册 ✅ 用户明确授权 ✅ 用户明确授权
转化率 ❌ 新用户无法完善信息 ❌ 老用户操作繁琐 ✅ 最优体验

五、后端接口说明

5.1 接口清单

接口 方法 用途
login/silentLogin POST 静默登录(新老用户判断)
login/authLogin POST 授权登录(新用户注册)
login/updateUser POST 更新用户信息(头像、昵称等)

5.2 静默登录接口 (login/silentLogin)

请求参数

javascript 复制代码
{
  code: "0a1s7M1w34ZqW63oy20w35APn20s7M10"  // uni.login() 获取的临时凭证
}

处理逻辑

  1. 使用 code 向微信服务器换取 openid
  2. 查询数据库中该 openid 是否存在
  3. 存在 → 返回 is_new_user: false
  4. 不存在 → 返回 is_new_user: true

返回数据

javascript 复制代码
{
  code: 1,
  msg: "success",
  data: {
    token: "eyJ0eXAiOiJKV1Qi...",
    is_new_user: false,  // 关键标识
    mobile: "13800138000",
    // 其他用户信息...
  }
}

5.3 授权登录接口 (login/authLogin)

请求参数

javascript 复制代码
{
  code: "0a1s7M1w34ZqW63oy20w35APn20s7M10",
  nickname: "张三",
  headimgurl: "https://..."
}

处理逻辑

  1. 使用 code 向微信服务器换取 openid
  2. 查询数据库中该 openid 是否存在
  3. 存在 → 老用户直接登录
  4. 不存在 → 创建新用户,完成注册

返回数据

javascript 复制代码
{
  code: 1,
  msg: "success",
  data: {
    token: "eyJ0eXAiOiJKV1Qi...",
    is_new_user: true,   // 关键标识
    mobile: null,
    // 其他用户信息...
  }
}

六、系统配置说明

6.1 配置项说明

在系统后台或配置接口中,有以下关键配置:

javascript 复制代码
{
  mnp_auto_wechat_auth: true,   // 是否开启小程序微信自动授权(静默登录)
  mnp_wechat_auth: true,        // 是否显示小程序微信登录按钮
  coerce_mobile: false,         // 是否强制绑定手机号
  login_way: [1, 2],           // 登录方式:1-密码登录,2-验证码登录
  register_way: [1],           // 注册方式
}

6.2 配置对登录流程的影响

配置项 true false
mnp_auto_wechat_auth 小程序启动时自动静默登录 不自动静默登录
mnp_wechat_auth 显示"用户一键登录"按钮 隐藏该按钮
coerce_mobile 未绑定手机号则终止流程 允许继续

七、快速接入指南

7.1 在新项目中实现双登录模式

步骤 1:创建工具文件
复制代码
utils/
  └─ login.js          // 登录相关工具函数
步骤 2:创建 API 文件
复制代码
api/
  └─ app.js            // 登录相关接口
步骤 3:创建登录页面
复制代码
pages/
  └─ login/
       └─ login.vue    // 登录页面
步骤 4:配置请求拦截器(关键步骤)

静默登录的触发入口在请求拦截器中,当 API 返回 code = -1 时自动触发:

javascript 复制代码
// utils/request.js

import { APICodeEnum } from './enum'
import { toLogin } from './login'

const events = {
    success({ data }) { return Promise.resolve(data) },
    fail({ msg }) { return Promise.reject(msg) },
    redirect({ msg }) {
        // #ifdef MP-WEIXIN
        if (store.getters.appConfig.mnp_status) {
            toLogin()  // 触发静默登录
        }
        // #endif
        store.commit('logout')
        return Promise.reject(msg)
    },
    closeShop({ msg }) { /* ... */ },
    tips({ code, msg }) { return { code, msg } }
}

// 响应拦截器
service.interceptors.response.use((response) => {
    const { msg, code, data, show } = response.data
    return events[APICodeEnum[code]](response.data)
})
步骤 5:配置路由守卫(辅助步骤)

当用户访问需要登录的页面时,路由守卫会拦截并跳转到登录页:

javascript 复制代码
// router.js

router.beforeEach((to, from, next) => {
    if (to.meta.auth && !store.getters.token) {
        next('/pages/login/login')
        return
    }
    next()
})
步骤 6:配置页面路由
json 复制代码
// pages.json

{
  "pages": [
    {
      "path": "pages/login/login",
      "style": {
        "navigationBarTitleText": "登录"
      }
    }
  ]
}

7.2 必要依赖

javascript 复制代码
// store/index.js (Vuex)

export default new Vuex.Store({
  state: {
    token: '',
    userInfo: {}
  },
  mutations: {
    login(state, data) {
      state.token = data.token;
      // 保存 token 到本地存储
    }
  },
  actions: {
    getUser({ commit }) {
      // 获取用户信息
    },
    getCartNum({ commit }) {
      // 获取购物车数量
    }
  },
  getters: {
    appConfig: state => state.app.config
  }
})

八、常见问题与排查

8.1 静默登录失败

现象:老用户也无法自动登录

排查步骤

  1. 检查后端 AppID 和 AppSecret 配置是否正确
  2. 检查 mnp_auto_wechat_auth 配置是否为 true
  3. 检查后端返回的 is_new_user 值是否正确

8.2 新用户无法注册

现象:点击授权登录后没有反应

排查步骤

  1. 检查是否同意服务协议和隐私协议
  2. 检查 mnp_wechat_auth 配置是否为 true
  3. 检查后端是否正确处理新用户注册逻辑

8.3 静默登录报错 "appid missing"

原因:后端未正确配置微信小程序的 AppID

解决:在服务器后台配置正确的 AppID 和 AppSecret


九、总结

9.1 核心要点

  1. 静默登录:老用户无感登录,新用户不执行登录
  2. 授权登录:新用户注册,获取头像昵称
  3. 配合机制 :通过 is_new_user 字段区分新老用户
  4. 用户体验:老用户打开即用,新用户一次授权

9.2 关键代码片段

javascript 复制代码
// 静默登录核心判断
if (loginData.token && !loginData.is_new_user) {
  // 仅老用户执行登录
}

// 授权登录核心判断
if (data.is_new_user) {
  // 新用户:显示完善信息弹窗
} else {
  // 老用户:直接完成登录
}

9.3 迁移到其他项目

  1. 复制 utils/login.js 到目标项目
  2. 复制 utils/enum.js 到目标项目(APICodeEnum 枚举定义)
  3. 复制 api/app.js 中的登录接口定义
  4. 复制 pages/login/login.vue 登录页面
  5. utils/request.js 响应拦截器中配置 code=-1 时调用 toLogin()
  6. router.js 中配置路由守卫,拦截需要登录的页面
  7. 确保后端接口返回正确的 is_new_user 字段
  8. 配置系统参数 mnp_auto_wechat_authmnp_wechat_auth

附录:完整文件清单

文件 用途
utils/login.js 登录工具函数(getCode、mnpLogin、toLogin 等)
utils/enum.js 接口返回码枚举(APICodeEnum,定义 code=-1 为 redirect)
utils/request.js 请求拦截器(响应拦截中 code=-1 触发 toLogin)
api/app.js 登录相关 API 接口定义
pages/login/login.vue 登录页面(授权登录入口)
router.js 路由守卫(meta.auth 页面拦截跳转登录页)
store/index.js Vuex 状态管理(login、getUser 等)
相关推荐
|晴 天|2 小时前
Vue 3 博客 SEO 优化:Meta 标签、Sitemap、Schema.org 实战
前端·vue.js·dreamweaver
Apple_羊先森2 小时前
# MOSS-TTS-Nano 教程 02:CLI 与 Web Demo 实战
前端·人工智能
Bat U2 小时前
JavaEE|多线程(三)
java·前端·java-ee
超级无敌暴龙兽11 小时前
和我一起刷面试题呀
前端·面试
wzl2026121311 小时前
企业微信定时群发技术实现与实操指南(原生接口+工具落地)
java·运维·前端·企业微信
小码哥_常11 小时前
Robots.txt:互联网爬虫世界的“隐形规则”
前端
小码哥_常12 小时前
Android开发神器:AndroidAutoSize,轻松搞定屏幕适配
前端
前端一小卒12 小时前
前端工程师的全栈焦虑,我用 60 天治好了
前端·javascript·后端
不停喝水12 小时前
【AI+Cursor】 告别切图仔,拥抱Vibe Coding: AI + Cursor 开启多模态全栈新纪元 (1)
前端·人工智能·后端·ai·ai编程·cursor