1. 微信小程序API扩展概述
微信小程序API扩展是指在原生API基础上进行功能增强和二次开发的技术实践。作为小程序开发者,我们经常会遇到官方API无法完全满足业务需求的场景。比如需要更灵活的支付流程、更复杂的蓝牙交互,或是定制化的地图功能。这时候就需要对原生API进行扩展封装。
我在多个电商和物联网项目中积累了一套行之有效的API扩展方案。通过合理的封装,不仅能够弥补官方API的功能缺口,还能显著提升开发效率和代码复用率。下面就以实际项目经验为例,分享几种常见的API扩展模式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础API扩展方案
2.1 请求类API封装
原生wx.request在使用中存在三个典型问题:
- 缺乏统一的错误处理
- 缺少请求拦截机制
- 重复的配置代码
我们可以这样改造:
javascript复制// http.js
const http = {
baseOptions(params, method = 'GET') {
let { url, data } = params
const token = wx.getStorageSync('token')
return new Promise((resolve, reject) => {
wx.request({
url: `https://api.example.com${url}`,
method,
data,
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
},
success(res) {
if (res.statusCode === 401) {
// 统一处理未授权
wx.navigateTo({ url: '/pages/login/index' })
return
}
resolve(res.data)
},
fail(err) {
// 统一错误处理
wx.showToast({ title: '网络异常', icon: 'none' })
reject(err)
}
})
})
},
get(url, data) {
return this.baseOptions({ url, data }, 'GET')
},
post(url, data) {
return this.baseOptions({ url, data }, 'POST')
}
}
使用示例:
javascript复制import http from './utils/http'
// 获取用户信息
http.get('/user/info').then(res => {
console.log(res)
})
// 提交订单
http.post('/order/create', { productId: 123 }).then(res => {
console.log(res)
})
2.2 存储API增强
原生wx.setStorage存在以下局限:
- 不支持自动过期
- 没有命名空间隔离
- 缺少类型检查
改进方案:
javascript复制// storage.js
const STORAGE_PREFIX = 'myApp_'
const storage = {
set(key, value, expire = 0) {
const data = {
value,
expire: expire > 0 ? Date.now() + expire * 1000 : 0
}
wx.setStorageSync(`${STORAGE_PREFIX}${key}`, data)
},
get(key) {
const data = wx.getStorageSync(`${STORAGE_PREFIX}${key}`)
if (!data) return null
if (data.expire > 0 && Date.now() > data.expire) {
this.remove(key)
return null
}
return data.value
},
remove(key) {
wx.removeStorageSync(`${STORAGE_PREFIX}${key}`)
}
}
使用示例:
javascript复制// 存储用户token,1小时后过期
storage.set('token', 'abc123', 3600)
// 获取token
const token = storage.get('token')
3. 高级API扩展实践
3.1 支付流程封装
微信小程序支付涉及多个API调用,流程复杂:
javascript复制// pay.js
const pay = {
async createOrder(orderInfo) {
try {
// 1. 调用后端创建预支付订单
const res = await http.post('/pay/create', orderInfo)
// 2. 调用微信支付API
const paymentRes = await new Promise((resolve, reject) => {
wx.requestPayment({
timeStamp: res.timeStamp,
nonceStr: res.nonceStr,
package: res.package,
signType: res.signType,
paySign: res.paySign,
success: resolve,
fail: reject
})
})
// 3. 验证支付结果
await http.post('/pay/verify', { orderId: res.orderId })
return { success: true }
} catch (err) {
console.error('支付失败:', err)
return { success: false, errMsg: err.errMsg || '支付失败' }
}
}
}
使用示例:
javascript复制// 发起支付
pay.createOrder({
productId: '123',
amount: 100
}).then(res => {
if (res.success) {
wx.showToast({ title: '支付成功' })
} else {
wx.showToast({ title: res.errMsg, icon: 'none' })
}
})
3.2 地图组件扩展
原生地图组件markers功能有限,我们可以扩展:
javascript复制// map.js
const map = {
createClusterMarkers(points, clusterDistance = 50) {
const clusters = []
points.forEach(point => {
let addedToCluster = false
clusters.forEach(cluster => {
const distance = this.calculateDistance(
point.latitude, point.longitude,
cluster.latitude, cluster.longitude
)
if (distance < clusterDistance) {
cluster.points.push(point)
addedToCluster = true
}
})
if (!addedToCluster) {
clusters.push({
...point,
points: [point],
isCluster: true,
clusterCount: 1
})
}
})
return clusters.map(cluster => {
if (cluster.points.length > 1) {
return {
id: `cluster_${cluster.id}`,
latitude: cluster.latitude,
longitude: cluster.longitude,
iconPath: '/images/cluster.png',
width: 40,
height: 40,
label: {
content: cluster.points.length.toString(),
color: '#fff',
fontSize: 12,
anchorX: 0,
anchorY: 0
}
}
} else {
return cluster.points[0]
}
})
},
calculateDistance(lat1, lng1, lat2, lng2) {
// 简化版距离计算
return Math.sqrt(Math.pow(lat1 - lat2, 2) + Math.pow(lng1 - lng2, 2)) * 111000
}
}
使用示例:
javascript复制Page({
data: {
markers: []
},
onLoad() {
const points = [
{ id: 1, latitude: 39.9042, longitude: 116.4074 },
{ id: 2, latitude: 39.9142, longitude: 116.4174 },
// 更多点...
]
this.setData({
markers: map.createClusterMarkers(points)
})
}
})
4. 常见问题与解决方案
4.1 授权管理封装
小程序授权流程复杂,可以这样简化:
javascript复制// auth.js
const auth = {
async checkPermission(scope) {
const res = await wx.getSetting()
if (!res.authSetting[scope]) {
const { confirm } = await wx.showModal({
title: '权限申请',
content: '需要获取您的授权',
confirmText: '去授权'
})
if (confirm) {
try {
await wx.authorize({ scope })
return true
} catch (err) {
await wx.openSetting()
return false
}
}
return false
}
return true
}
}
使用示例:
javascript复制// 检查相机权限
auth.checkPermission('scope.camera').then(hasAuth => {
if (hasAuth) {
wx.chooseImage({ count: 1 })
}
})
4.2 图片403问题处理
微信小程序中图片403常见于防盗链,解决方案:
javascript复制// image.js
const image = {
fixUrl(url) {
if (!url) return ''
// 处理相对路径
if (url.startsWith('/')) {
return `https://yourdomain.com${url}`
}
// 处理防盗链
if (url.includes('http') && !url.includes('yourdomain.com')) {
return `https://yourproxy.com?url=${encodeURIComponent(url)}`
}
return url
}
}
使用示例:
html复制<image src="{{image.fixUrl(item.picUrl)}}" mode="aspectFill"></image>
4.3 WebView通信优化
小程序与WebView通信的可靠方案:
javascript复制// webview.js
const webview = {
postMessage(webviewId, data) {
return new Promise((resolve) => {
const currentWebview = this.getWebviewById(webviewId)
currentWebview.postMessage({ data })
// 设置超时
setTimeout(() => {
resolve(false)
}, 3000)
// 监听回执
wx.onMessage((msg) => {
if (msg.type === 'ack') {
resolve(true)
}
})
})
},
getWebviewById(id) {
const pages = getCurrentPages()
const page = pages.find(p => p.__wxWebviewId__ === id)
return page ? page.$el : null
}
}
H5端对应代码:
javascript复制window.addEventListener('message', (e) => {
// 处理小程序消息
console.log('收到消息:', e.data)
// 发送回执
wx.miniProgram.postMessage({ type: 'ack' })
})
5. 性能优化技巧
5.1 API调用节流
高频API调用需要节流:
javascript复制// throttle.js
function throttle(fn, delay = 500) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
// 使用示例
Page({
onPageScroll: throttle(function(e) {
console.log('滚动事件:', e)
}, 200)
})
5.2 数据缓存策略
javascript复制// cache.js
const cache = {
async getWithCache(key, fetchFn, expire = 60) {
const cached = storage.get(key)
if (cached) return cached
const freshData = await fetchFn()
storage.set(key, freshData, expire)
return freshData
}
}
// 使用示例
const userInfo = await cache.getWithCache(
'user_info',
() => http.get('/user/info'),
3600
)
5.3 预加载关键API
javascript复制// preload.js
const preload = {
criticalApis: ['login', 'getSystemInfo'],
init() {
this.criticalApis.forEach(api => {
wx[api]()
})
}
}
// App.js
App({
onLaunch() {
preload.init()
}
})
6. 调试与监控
6.1 API调用日志
javascript复制// logger.js
const logger = {
logApiCall(apiName, params, success, costTime) {
if (!__wxConfig.debug) return
console.groupCollapsed(`[API] ${apiName} ${success ? '✓' : '✗'}`)
console.log('Params:', params)
console.log('Cost:', costTime, 'ms')
console.groupEnd()
// 上报监控系统
wx.request({
url: 'https://monitor.example.com/api/log',
method: 'POST',
data: {
apiName,
success,
costTime,
timestamp: Date.now()
}
})
}
}
// 封装原API
const originalRequest = wx.request
wx.request = function(options) {
const start = Date.now()
return originalRequest({
...options,
success(res) {
logger.logApiCall(
options.url,
options.data,
true,
Date.now() - start
)
options.success && options.success(res)
},
fail(err) {
logger.logApiCall(
options.url,
options.data,
false,
Date.now() - start
)
options.fail && options.fail(err)
}
})
}
6.2 性能监控
javascript复制// performance.js
const performance = {
metrics: {},
start(name) {
this.metrics[name] = {
start: Date.now()
}
},
end(name) {
const metric = this.metrics[name]
if (!metric) return
metric.end = Date.now()
metric.duration = metric.end - metric.start
console.log(`[Performance] ${name}: ${metric.duration}ms`)
return metric.duration
}
}
// 使用示例
performance.start('page_load')
Page({
onReady() {
const loadTime = performance.end('page_load')
// 上报到监控系统
}
})
7. 安全加固方案
7.1 请求签名
javascript复制// sign.js
const sign = {
generate(params, secret) {
const keys = Object.keys(params).sort()
let str = ''
keys.forEach(key => {
str += `${key}=${params[key]}&`
})
str += `key=${secret}`
return md5(str).toUpperCase()
}
}
// 在http.js中集成
const http = {
baseOptions(params) {
const timestamp = Date.now()
const nonce = Math.random().toString(36).substr(2)
const signStr = sign.generate({
...params.data,
timestamp,
nonce
}, 'your_secret_key')
return wx.request({
...params,
header: {
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Sign': signStr
}
})
}
}
7.2 敏感数据保护
javascript复制// security.js
const security = {
encrypt(data, key) {
// 简单示例,实际应使用更安全的加密算法
let result = ''
for (let i = 0; i < data.length; i++) {
const charCode = data.charCodeAt(i) ^ key.charCodeAt(i % key.length)
result += String.fromCharCode(charCode)
}
return btoa(result)
},
decrypt(encrypted, key) {
const data = atob(encrypted)
let result = ''
for (let i = 0; i < data.length; i++) {
const charCode = data.charCodeAt(i) ^ key.charCodeAt(i % key.length)
result += String.fromCharCode(charCode)
}
return result
}
}
// 使用示例
const encrypted = security.encrypt('敏感数据', 'secret_key')
const original = security.decrypt(encrypted, 'secret_key')
8. 跨平台兼容方案
8.1 环境判断
javascript复制// env.js
const env = {
isWechatMiniProgram() {
return typeof wx !== 'undefined' && wx && wx.request
},
isWeb() {
return typeof window !== 'undefined'
},
isApp() {
return typeof uni !== 'undefined'
}
}
8.2 统一API适配层
javascript复制// adapter.js
const adapter = {
request(options) {
if (env.isWechatMiniProgram()) {
return wx.request(options)
} else if (env.isWeb()) {
return fetch(options.url, {
method: options.method,
body: JSON.stringify(options.data),
headers: options.header
}).then(res => res.json())
} else if (env.isApp()) {
return uni.request(options)
}
}
}
9. 项目实战案例
9.1 电商小程序API扩展
典型电商小程序需要的扩展API:
javascript复制// ecommerce.js
const ecommerce = {
async addToCart(productId, skuId, quantity = 1) {
try {
const res = await http.post('/cart/add', {
productId,
skuId,
quantity
})
// 更新本地购物车数量
const cartCount = this.getCartCount() + quantity
storage.set('cart_count', cartCount)
wx.showToast({ title: '加入购物车成功' })
return res
} catch (err) {
wx.showToast({ title: err.message, icon: 'none' })
throw err
}
},
getCartCount() {
return storage.get('cart_count') || 0
},
async getProductDetail(id) {
return cache.getWithCache(
`product_${id}`,
() => http.get(`/product/${id}`),
1800 // 缓存30分钟
)
}
}
9.2 物联网小程序API扩展
物联网项目常用扩展:
javascript复制// iot.js
const iot = {
devices: new Map(),
async connectDevice(deviceId) {
if (this.devices.has(deviceId)) {
return this.devices.get(deviceId)
}
const device = await wx.connectBLEDevice({
deviceId,
timeout: 10000
})
this.devices.set(deviceId, device)
return device
},
async sendCommand(deviceId, command) {
const device = await this.connectDevice(deviceId)
return new Promise((resolve, reject) => {
device.writeBLECharacteristicValue({
serviceId: '0000FFE0-0000-1000-8000-00805F9B34FB',
characteristicId: '0000FFE1-0000-1000-8000-00805F9B34FB',
value: this.stringToArrayBuffer(command),
success: resolve,
fail: reject
})
})
},
stringToArrayBuffer(str) {
const buf = new ArrayBuffer(str.length)
const bufView = new Uint8Array(buf)
for (let i = 0; i < str.length; i++) {
bufView[i] = str.charCodeAt(i)
}
return buf
}
}
10. 持续维护建议
10.1 API版本管理
javascript复制// version.js
const version = {
current: '1.0.0',
checkUpdate() {
http.get('/api/version').then(res => {
if (this.compareVersions(res.version, this.current) > 0) {
wx.showModal({
title: '发现新版本',
content: '是否立即更新?',
success: ({ confirm }) => {
if (confirm) {
this.applyUpdate()
}
}
})
}
})
},
compareVersions(v1, v2) {
const parts1 = v1.split('.').map(Number)
const parts2 = v2.split('.').map(Number)
for (let i = 0; i < 3; i++) {
if (parts1[i] > parts2[i]) return 1
if (parts1[i] < parts2[i]) return -1
}
return 0
},
applyUpdate() {
const updateManager = wx.getUpdateManager()
updateManager.onCheckForUpdate((hasUpdate) => {
if (!hasUpdate) {
wx.showToast({ title: '已是最新版本' })
return
}
})
updateManager.onUpdateReady(() => {
wx.showModal({
title: '更新提示',
content: '新版本下载完成,是否重启应用?',
success: ({ confirm }) => {
if (confirm) {
updateManager.applyUpdate()
}
}
})
})
}
}
10.2 错误监控系统集成
javascript复制// error.js
const error = {
init() {
// 监听未捕获的Promise错误
wx.onUnhandledRejection((res) => {
this.report({
type: 'unhandledRejection',
reason: res.reason,
promise: res.promise
})
})
// 监听小程序错误
wx.onError((err) => {
this.report({
type: 'jsError',
message: err.message,
stack: err.stack
})
})
// 监听页面不存在错误
wx.onPageNotFound((res) => {
this.report({
type: 'pageNotFound',
path: res.path,
query: res.query
})
})
},
report(data) {
const errorData = {
...data,
appVersion: version.current,
timestamp: Date.now(),
platform: wx.getSystemInfoSync().platform
}
// 本地存储
const errors = storage.get('error_logs') || []
errors.push(errorData)
storage.set('error_logs', errors)
// 上报服务器
if (wx.getNetworkType().networkType !== 'none') {
http.post('/monitor/error', errorData).catch(() => {
// 上报失败忽略
})
}
},
uploadStoredErrors() {
const errors = storage.get('error_logs')
if (errors && errors.length > 0) {
http.post('/monitor/errors', { errors }).then(() => {
storage.remove('error_logs')
})
}
}
}
