8.1 微信登录体系
微信小程序的登录体系基于 OpenID(用户在该小程序的唯一标识)和 UnionID(同一主体下所有微信产品的统一标识)。整个登录流程由前端、后端和微信服务器三方协同完成。
code
wx.login 返回的临时凭证,有效期 5 分钟,只能使用一次。不能直接在前端使用,必须发送到后端
openid
用户在某个小程序的唯一标识。同一用户在不同小程序的 openid 不同
unionid
同一微信开放平台账号下的多个应用共享同一 unionid,用于跨应用识别用户身份
session_key
会话密钥,服务端保存,用于解密用户敏感信息(手机号等)。绝对不能传给前端
完整登录流程
// ─── 前端(小程序)───
// utils/auth.js
const http = require('./request')
async function login() {
// Step 1:调用 wx.login 获取 code
const { code } = await wx.login()
console.log('临时 code:', code)
// Step 2:将 code 发送给自己的后端,换取 token
const res = await http.post('/auth/wx-login', { code })
// res 包含 { token, userInfo }
// Step 3:存储 token
wx.setStorageSync('token', res.token)
const app = getApp()
app.globalData.token = res.token
app.globalData.isLoggedIn = true
app.globalData.userInfo = res.userInfo
return res
}
// ─── 后端(Node.js 示例)───
// POST /auth/wx-login
async function wxLogin(req, res) {
const { code } = req.body
// Step A:用 code 换取 openid + session_key
const wxRes = await fetch(
`https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${code}&grant_type=authorization_code`
).then(r => r.json())
const { openid, session_key, unionid } = wxRes
// Step B:查找或创建用户
let user = await User.findOne({ openid })
if (!user) {
user = await User.create({ openid, unionid, createdAt: new Date() })
}
// Step C:生成 JWT,session_key 存 Redis(不传前端!)
await redis.setEx(`sk:${openid}`, 7200, session_key)
const token = jwt.sign({ openid, userId: user._id }, JWT_SECRET, { expiresIn: '7d' })
res.json({ token, userInfo: user })
}
获取手机号(敏感能力)
<!-- WXML:使用 button 的 open-type="getPhoneNumber" -->
<button
open-type="getPhoneNumber"
bindgetphonenumber="onGetPhone"
class="btn-primary"
>
手机号一键绑定
</button>
// 获取手机号回调
async onGetPhone(e) {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
return // 用户拒绝
}
// code 发给后端,后端用 session_key 解密手机号
const { code } = e.detail
await http.post('/user/bind-phone', { code })
wx.showToast({ title: '绑定成功' })
}
安全红线:session_key 不能传给前端
session_key 是解密用户手机号、位置等敏感信息的密钥,绝对不能通过接口返回给小程序前端。只能在服务器端使用,并设置合理的过期时间(微信也会定期更新 session_key)。一旦泄露,用户隐私数据将暴露。
8.2 微信支付
小程序内支付使用 JSAPI 模式,流程涉及小程序前端、自有后端和微信支付服务器三方。
支付流程
- 用户点击支付,前端调用后端创建订单接口,传入商品信息
- 后端调用微信支付「统一下单 API」(v3 版本:POST /v3/pay/transactions/jsapi),获取 prepay_id
- 后端对支付参数进行签名,将签名后的参数返回给前端(timeStamp、nonceStr、package、signType、paySign)
- 前端调用
wx.requestPayment拉起收银台 - 用户完成支付后,微信服务器异步通知后端(notify_url)
- 后端核实订单金额,更新订单状态
// 前端支付代码
async onPay() {
wx.showLoading({ title: '创建订单...' })
try {
// 1. 创建订单,获取支付参数
const payParams = await http.post('/orders', {
productId: this.data.productId,
quantity: this.data.quantity
})
// payParams: { timeStamp, nonceStr, package, signType, paySign }
wx.hideLoading()
// 2. 调起收银台
await wx.requestPayment(payParams)
// 3. 支付成功(注意:这里只代表 wx.requestPayment 成功,不代表后端已核实)
wx.showToast({ title: '支付成功' })
wx.navigateTo({ url: `/pages/order-result/order-result?orderId=${payParams.orderId}` })
} catch (e) {
wx.hideLoading()
if (e.errMsg === 'requestPayment:fail cancel') {
wx.showToast({ title: '已取消支付', icon: 'none' })
} else {
wx.showToast({ title: '支付失败,请重试', icon: 'error' })
}
}
}
8.3 订阅消息
订阅消息允许小程序在用户授权后,在特定场景(如订单发货、预约提醒)向用户发送服务通知。2019 年取代了旧版模板消息。
订阅流程
- 在微信公众平台「功能 → 订阅消息」中选择或申请消息模板,获取模板 ID
- 在用户发生相关操作时(如下单),调用
wx.requestSubscribeMessage请求授权 - 用户同意后,在服务端触发事件时调用微信「发送订阅消息」API 推送通知
// 在下单成功后请求订阅"发货提醒"
async requestSubscribe() {
try {
const res = await wx.requestSubscribeMessage({
tmplIds: [
'模板ID_发货提醒',
'模板ID_签收确认'
]
})
// res: { '模板ID_发货提醒': 'accept' | 'reject' | 'ban' }
const accepted = Object.entries(res)
.filter(([_, status]) => status === 'accept')
.map(([tmplId]) => tmplId)
// 将用户同意的模板 ID 发给后端保存
if (accepted.length > 0) {
await http.post('/user/subscribe', { tmplIds: accepted })
}
} catch (e) {
// 用户未授权或已禁用,静默处理(不强制要求)
console.log('订阅取消', e)
}
}
// 后端发送订阅消息(Node.js)
async function sendShipNotice(openid, orderInfo) {
// 获取 access_token(有效期 2 小时,建议缓存)
const accessToken = await getAccessToken()
await fetch(
`https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${accessToken}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
touser: openid,
template_id: '模板ID_发货提醒',
page: `/pages/order-detail/index?id=${orderInfo.orderId}`,
data: {
thing1: { value: orderInfo.productName }, // 商品名称
character_string2: { value: orderInfo.orderId }, // 订单号
thing3: { value: '顺丰快递' }, // 物流公司
character_string4: { value: orderInfo.trackingNo } // 快递单号
}
})
}
)
}
订阅消息的使用规范
每次用户授权只能发一条订阅消息(一次性授权)。若用户在手机端设置了「长期订阅」,则可以多次发送。不要在与发送场景无关的时机请求订阅,否则用户体验差且容易被用户永久拒绝。请求订阅消息必须在用户主动操作后触发(不能在页面 onLoad 中调用)。