以下是对微信小程序开放能力中核心知识点的详细整理,包括每个功能的原理说明、使用方法、完整代码示例(含详细注释),以及综合性案例。
微信小程序开放能力详解
1. 微信登录与授权
1.1 小程序登录流程
小程序登录用于获取用户唯一标识(openid)和会话密钥(session_key),用于后续用户身份识别和数据解密。
登录流程:
- 小程序调用
wx.login()
获取临时登录凭证(code) - 将 code 发送到开发者服务器
- 服务器用 code + AppID + AppSecret 向微信接口换取 openid 和 session_key
- 服务器生成自定义登录态(如 token)返回给小程序
前端代码(小程序端):
// pages/login/login.js
Page({
onLoad() {
this.wxLogin();
},
async wxLogin() {
try {
// 1. 获取临时登录凭证 code
const res = await wx.login();
const code = res.code;
if (!code) {
console.error('登录失败!获取 code 失败');
return;
}
// 2. 发送 code 到开发者服务器
const serverRes = await wx.request({
url: 'https://your-server.com/api/login', // 替换为你的后端地址
method: 'POST',
data: { code },
header: { 'content-type': 'application/json' }
});
const { token, openid } = serverRes.data;
if (token) {
// 3. 保存 token 到本地(用于后续请求认证)
wx.setStorageSync('token', token);
console.log('登录成功,openid:', openid);
} else {
console.error('服务器登录失败');
}
} catch (err) {
console.error('登录异常:', err);
}
}
});
后端示例(Node.js + Express):
// server.js
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
app.post('/api/login', async (req, res) => {
const { code } = req.body;
const appId = 'YOUR_APPID';
const appSecret = 'YOUR_APPSECRET';
try {
// 向微信服务器换取 openid 和 session_key
const wxRes = await axios.get(
`https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`
);
const { openid, session_key } = wxRes.data;
if (!openid) {
return res.status(400).json({ error: '获取 openid 失败' });
}
// 此处应生成自定义 token(如 JWT)
const token = generateToken(openid); // 自定义函数
// 可选:将 session_key 存入数据库(用于后续解密)
saveSession(openid, session_key);
res.json({ token, openid });
} catch (err) {
console.error('登录失败:', err);
res.status(500).json({ error: '服务器错误' });
}
});
function generateToken(openid) {
// 简化:实际应使用 JWT 等安全机制
return Buffer.from(openid).toString('base64');
}
2. 小程序授权管理
2.1 获取用户信息(需用户主动触发)
⚠️ 自 2021 年 4 月起,
wx.getUserInfo
不再返回用户信息,必须通过 按钮触发 +<button open-type="getUserInfo">
获取。
前端代码:
<!-- pages/profile/profile.wxml -->
<button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">
获取用户信息
</button>
// pages/profile/profile.js
Page({
onGetUserInfo(e) {
const { userInfo, rawData, signature, encryptedData, iv } = e.detail;
if (userInfo) {
// 用户已授权,可直接使用 userInfo
console.log('用户信息:', userInfo);
wx.setStorageSync('userInfo', userInfo);
} else {
// 用户拒绝授权
wx.showToast({ title: '授权失败', icon: 'none' });
}
}
});
💡 注意:
encryptedData
和iv
可用于后端解密获取更完整的用户数据(如 unionid)。
3. 开放数据校验与解密
3.1 解密用户敏感数据(如手机号、用户信息)
需在后端使用 session_key
对 encryptedData
和 iv
进行 AES 解密。
小程序端获取手机号(按钮触发):
<!-- pages/phone/phone.wxml -->
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
获取手机号
</button>
// pages/phone/phone.js
Page({
getPhoneNumber(e) {
const { encryptedData, iv, cloudID } = e.detail;
if (!encryptedData || !iv) {
wx.showToast({ title: '获取失败', icon: 'none' });
return;
}
// 发送加密数据到后端解密
wx.request({
url: 'https://your-server.com/api/decrypt-phone',
method: 'POST',
data: { encryptedData, iv, openid: wx.getStorageSync('openid') },
success(res) {
console.log('解密后的手机号:', res.data.phoneNumber);
}
});
}
});
后端解密(Node.js):
const crypto = require('crypto');
function decryptData(encryptedData, iv, sessionKey) {
// Base64 解码
const encrypted = Buffer.from(encryptedData, 'base64');
const ivBuf = Buffer.from(iv, 'base64');
const sessionKeyBuf = Buffer.from(sessionKey, 'base64');
// AES-128-CBC 解密
let decipher = crypto.createDecipheriv('aes-128-cbc', sessionKeyBuf, ivBuf);
decipher.setAutoPadding(false);
let decoded = decipher.update(encrypted, '', 'binary');
decoded += decipher.final('binary');
// 去除 PKCS#7 填充
const pad = decoded.charCodeAt(decoded.length - 1);
decoded = decoded.substring(0, decoded.length - pad);
return JSON.parse(decoded);
}
// 路由处理
app.post('/api/decrypt-phone', (req, res) => {
const { encryptedData, iv, openid } = req.body;
const sessionKey = getSessionKeyFromDB(openid); // 从数据库获取之前保存的 session_key
try {
const phoneInfo = decryptData(encryptedData, iv, sessionKey);
res.json({ phoneNumber: phoneInfo.phoneNumber });
} catch (err) {
res.status(400).json({ error: '解密失败' });
}
});
4. 微信支付
4.1 小程序支付流程
- 用户在小程序下单
- 小程序请求开发者服务器创建订单
- 服务器调用微信统一下单 API,获取
prepay_id
- 服务器返回支付参数给小程序
- 小程序调用
wx.requestPayment
发起支付
小程序端发起支付:
// pages/order/pay.js
Page({
async payOrder() {
try {
// 1. 请求后端创建订单并获取支付参数
const res = await wx.request({
url: 'https://your-server.com/api/create-order',
method: 'POST',
data: { goodsId: '123', total: 100 }
});
const { timeStamp, nonceStr, package, signType, paySign } = res.data;
// 2. 调用微信支付
await wx.requestPayment({
timeStamp, // 时间戳(字符串)
nonceStr, // 随机字符串
package, // 统一下单返回的 prepay_id 格式:prepay_id=***
signType, // 签名算法,通常为 'MD5' 或 'HMAC-SHA256'
paySign // 签名
});
wx.showToast({ title: '支付成功' });
} catch (err) {
wx.showToast({ title: '支付失败', icon: 'none' });
}
}
});
后端统一下单(Node.js 示例):
const crypto = require('crypto');
function sign(params, key) {
// 按 key 字典序排序
const sorted = Object.keys(params)
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
const str = sorted + '&key=' + key;
return crypto.createHash('md5').update(str, 'utf8').digest('hex').toUpperCase();
}
app.post('/api/create-order', async (req, res) => {
const { goodsId, total, openid } = req.body;
// 调用微信统一下单接口
const params = {
appid: 'YOUR_APPID',
mch_id: 'YOUR_MCH_ID',
nonce_str: Math.random().toString(36).substr(2, 15),
body: '商品支付',
out_trade_no: 'ORDER_' + Date.now(), // 商户订单号
total_fee: total, // 单位:分
spbill_create_ip: '127.0.0.1',
notify_url: 'https://your-server.com/api/pay-notify',
trade_type: 'JSAPI',
openid: openid
};
params.sign = sign(params, 'YOUR_API_KEY'); // 商户 API 密钥
const xml = objectToXml(params); // 需实现 XML 序列化
const wxRes = await axios.post('https://api.mch.weixin.qq.com/pay/unifiedorder', xml, {
headers: { 'Content-Type': 'application/xml' }
});
const result = xmlToJs(wxRes.data); // 需实现 XML 解析
if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') {
const prepayId = result.prepay_id;
// 构造小程序支付参数
const payParams = {
appId: 'YOUR_APPID',
timeStamp: Math.floor(Date.now() / 1000).toString(),
nonceStr: Math.random().toString(36).substr(2, 15),
package: `prepay_id=${prepayId}`,
signType: 'MD5'
};
payParams.paySign = sign(payParams, 'YOUR_API_KEY');
res.json(payParams);
} else {
res.status(400).json({ error: '下单失败' });
}
});
5. 分享、收藏与转发
5.1 自定义分享内容
Page({
onShareAppMessage() {
return {
title: '快来一起使用这个小程序!',
path: '/pages/index/index?from=share',
imageUrl: 'https://example.com/share.jpg' // 自定义分享图
};
}
});
⚠️ 必须用户主动点击页面右上角菜单或
button open-type="share"
才能触发。
6. 小程序订阅消息
6.1 引导用户订阅消息
// 请求订阅
wx.requestSubscribeMessage({
tmplIds: ['模板ID1', '模板ID2'],
success(res) {
console.log('订阅成功:', res);
},
fail(err) {
console.error('订阅失败:', err);
}
});
6.2 后端发送订阅消息
// 获取 access_token
const tokenRes = await axios.get(
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET`
);
const accessToken = tokenRes.data.access_token;
// 发送消息
await axios.post(
`https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${accessToken}`,
{
touser: openid,
template_id: '模板ID',
page: '/pages/order/detail?id=123',
data: {
thing1: { value: '订单已发货' },
time2: { value: '2025-10-10 10:00' }
}
}
);
综合性案例:电商小程序核心流程
场景:用户登录 → 浏览商品 → 下单支付 → 支付成功后发送订阅消息
1. 登录并获取用户信息
// app.js
App({
onLaunch() {
wx.login().then(res => {
// 发送 code 到后端登录
});
}
});
2. 商品页“立即购买”按钮
<button bindtap="buyNow">立即购买</button>
3. 支付成功后订阅通知
// 支付成功回调中
wx.requestPayment({ ... }).then(() => {
// 弹出订阅弹窗
wx.requestSubscribeMessage({
tmplIds: ['ORDER_STATUS_TEMPLATE_ID'],
success() {
// 可选:记录用户已订阅
}
});
// 跳转到订单页
wx.navigateTo({ url: '/pages/order/success' });
});
4. 后端在发货时发送订阅消息
// 当订单状态变为“已发货”
sendSubscribeMessage(openid, {
thing1: { value: '您的订单已发货' },
time2: { value: new Date().toLocaleString() }
});
本章小结
功能 | 是否需用户触发 | 是否需后端参与 | 安全要求 |
---|---|---|---|
登录 | 否(自动) | 是 | session_key 保密 |
获取用户信息 | 是(按钮) | 否(基础信息)/是(完整信息) | encryptedData 解密需 session_key |
获取手机号 | 是(按钮) | 是 | 必须后端解密 |
微信支付 | 是(用户确认) | 是 | 签名验证、HTTPS |
订阅消息 | 是(用户授权) | 是 | 模板 ID 审核 |
分享 | 是(菜单/按钮) | 否 | 无 |
📌 所有涉及用户隐私或支付的操作,必须通过后端完成关键逻辑,前端仅负责交互和展示。
如需进一步扩展(如云开发简化后端、使用云函数解密等),可结合微信云开发能力实现无服务器架构。