微信小程序登录 + 自定义登录态的全链路实现

作者:Administrator 发布时间: 2025-12-18 阅读量:6 评论数:0

这是一个非常标准的微信小程序登录 + 自定义登录态的全链路实现。

为了让你直接能用,我将分为 Node.js 后端 (Express)小程序前端 两部分。

核心流程图解

  1. 前端wx.login() 拿到临时 code -> 发给后端。

  2. 后端:拿 code + appid + appsecret 去微信服务器换取 openidsession_key

  3. 后端:生成自己的 Token (JWT),返回给前端。

  4. 前端:存 Token,以后请求带上它。


第一部分:Node.js 后端实现 (Express)

你需要安装以下依赖:

Bash

npm install express axios jsonwebtoken body-parser

server.js 代码:

JavaScript

const express = require('express');
const axios = require('axios');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

// ================= 配置区 =================
const WX_CONFIG = {
  appid: '你的小程序APPID',
  secret: '你的小程序APPSECRET',
};
const JWT_SECRET = 'your_jwt_secret_key'; // 用于签发 Token 的密钥

// ================= 登录接口 =================
app.post('/api/login', async (req, res) => {
  const { code } = req.body;

  if (!code) {
    return res.status(400).json({ error: '缺少 code 参数' });
  }

  try {
    // 1. 向微信服务器发送请求,换取 openid 和 session_key
    const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${WX_CONFIG.appid}&secret=${WX_CONFIG.secret}&js_code=${code}&grant_type=authorization_code`;
    
    const wxRes = await axios.get(url);
    const { openid, session_key, errcode, errmsg } = wxRes.data;

    // 检查微信返回的错误
    if (errcode) {
      console.error('微信 API 报错:', errmsg);
      return res.status(500).json({ error: '微信登录失败', detail: errmsg });
    }

    // 2. 【核心业务逻辑】查询数据库,查找或注册用户
    // 这里是模拟数据库操作
    let user = await mockFindUserByOpenid(openid);
    if (!user) {
      user = await mockCreateUser(openid); // 如果是新用户,存入数据库
    }

    // 3. 生成自定义 Token (JWT)
    // 注意:千万不要把 session_key 直接返回给前端,也不要放在 token 里,除非你需要解密手机号
    const token = jwt.sign(
      { userId: user.id, openid: openid }, 
      JWT_SECRET, 
      { expiresIn: '7d' } // Token 有效期 7 天
    );

    // 4. 返回 Token 给前端
    res.json({
      code: 0,
      msg: 'Login success',
      data: {
        token: token,
        userInfo: user // 可选:返回一些用户信息
      }
    });

  } catch (error) {
    console.error(error);
    res.status(500).json({ error: '服务器内部错误' });
  }
});

// ================= 中间件:验证 Token =================
// 用于保护其他接口,确保用户已登录
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  // 格式通常是: "Bearer <token>"
  const token = authHeader && authHeader.split(' ')[1]; 

  if (!token) return res.sendStatus(401);

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user; // 把解密出来的用户信息挂载到 req 上
    next();
  });
};

// 测试受保护的接口
app.get('/api/profile', authMiddleware, (req, res) => {
  res.json({ msg: '这是受保护的数据', currentUser: req.user });
});

// ================= 模拟数据库函数 =================
async function mockFindUserByOpenid(openid) {
  // 实际开发中请使用 db.collection('users').findOne({ openid })
  console.log(`正在查找用户: ${openid}`);
  return null; // 模拟未找到,走注册流程
}

async function mockCreateUser(openid) {
  console.log(`正在注册新用户: ${openid}`);
  return { id: 1001, nickname: '微信用户', openid };
}

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

第二部分:小程序前端实现

建议封装一个 login.js 工具文件,或者直接写在 app.js 里。

utils/request.js (简单的请求封装,自动带 Token)

JavaScript

// 基础 URL
const BASE_URL = 'http://localhost:3000';

function request(url, method = 'GET', data = {}) {
  const token = wx.getStorageSync('token'); // 从本地缓存拿 Token

  return new Promise((resolve, reject) => {
    wx.request({
      url: BASE_URL + url,
      method: method,
      data: data,
      header: {
        'Content-Type': 'application/json',
        'Authorization': token ? `Bearer ${token}` : '' // 自动携带 Token
      },
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.data);
        } else if (res.statusCode === 401) {
          // Token 过期,可以在这里触发重新登录逻辑
          wx.showToast({ title: '登录过期', icon: 'none' });
          reject(res);
        } else {
          reject(res);
        }
      },
      fail: (err) => reject(err)
    });
  });
}

module.exports = { request };

pages/login/login.js (执行登录逻辑)

JavaScript

const { request } = require('../../utils/request');

Page({
  // 点击登录按钮
  handleLogin() {
    wx.showLoading({ title: '登录中...' });

    // 1. 调用微信登录接口
    wx.login({
      success: async (res) => {
        if (res.code) {
          try {
            // 2. 把 code 发给后端
            const result = await request('/api/login', 'POST', {
              code: res.code
            });

            console.log('后端返回结果:', result);

            if (result.code === 0) {
              // 3. 登录成功,保存 Token 到本地
              wx.setStorageSync('token', result.data.token);
              wx.setStorageSync('userInfo', result.data.userInfo);
              
              wx.showToast({ title: '登录成功' });
              
              // 4. 跳转到首页或测试受保护接口
              this.testProtectedApi();
            }
          } catch (error) {
            console.error('登录业务失败', error);
            wx.showToast({ title: '登录服务器报错', icon: 'none' });
          } finally {
            wx.hideLoading();
          }
        } else {
          console.log('登录失败!' + res.errMsg);
        }
      }
    });
  },

  // 测试请求受保护的接口
  async testProtectedApi() {
    try {
      const res = await request('/api/profile', 'GET');
      console.log('受保护数据:', res);
    } catch (err) {
      console.error(err);
    }
  }
});

面试/实战中的关键点 (FAQ)

  1. 为什么不能在前端直接请求微信的 jscode2session 接口?

    • 安全问题: 微信明确禁止。因为这需要把 AppSecret 暴露在前端代码中,一旦泄露,黑客可以冒充你的小程序身份。

  2. session_key 有什么用?

    • 它是用来解密开放数据(如用户手机号、运动步数)的密钥。

    • 注意: 不要把它存到数据库里长期使用,它会过期。一般建议存在 Redis 里,或者用到的时候重新 wx.login 获取。

  3. Token 过期了怎么处理?

    • 在前端封装的 request 中拦截 401 状态码。

    • 一旦发现 401,静默调用 wx.login 重新走一遍登录流程(无感刷新),或者弹窗提示用户重新点击登录。

评论