微信小程序云开发环境搭建与REST API混合架构实战
本文介绍如何在微信小程序中同时使用云开发(云函数、数据库)和外部REST API,构建灵活高效的混合架构,并解决跨域、鉴权等常见问题。
一、为什么需要混合架构?
微信小程序云开发提供了便捷的云函数和云数据库,但在实际项目中,我们经常遇到以下场景:
- 需要对接企业已有的业务系统(ERP、CRM、支付网关)
- 需要使用第三方AI服务(腾讯混元、百度文心一言)
- 需要调用外部数据源(天气API、物流API)
此时,混合架构就是最佳选择:核心业务数据走云开发保证安全与稳定,外部接口走REST API保证灵活性。
二、项目结构设计
miniprogram/
├── cloudfunctions/ # 云函数目录
│ └── callExternalApi/ # 云函数:代理外部API
├── pages/ # 页面
├── services/ # 业务服务层
│ ├── api.js # REST API 封装
│ └── cloudDb.js # 云数据库封装
└── utils/
└── auth.js # 鉴权工具
三、云函数:代理外部API(解决跨域问题)
在小程序端直接调用外部API会面临跨域限制。正确的做法是通过云函数作为代理层。
3.1 创建云函数目录
bash
mkdir cloudfunctions/callExternalApi
cd cloudfunctions/callExternalApi
3.2 云函数入口文件
javascript
// cloudfunctions/callExternalApi/index.js
const cloud = require('wx-server-sdk')
const axios = require('axios') // 可在云函数中引入 npm 包
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
exports.main = async (event, context) => {
const { url, method = 'GET', data = {}, headers = {} } = event
try {
// 构建请求配置
const config = {
method,
url,
headers: {
'Content-Type': 'application/json',
...headers
},
timeout: 10000 // 10秒超时
}
// GET请求参数放params,POST/PUT放data
if (method === 'GET') {
config.params = data
} else {
config.data = data
}
// 调用外部API
const response = await axios(config)
return {
success: true,
data: response.data
}
} catch (error) {
console.error('External API call failed:', error.message)
return {
success: false,
error: error.message
}
}
}
3.3 package.json(云函数依赖)
json
{
"name": "call-external-api",
"version": "1.0.0",
"dependencies": {
"wx-server-sdk": "~2.6.3",
"axios": "^1.6.0"
}
}
注意 :云函数上传时需要先在本地安装依赖,再上传整个目录。也可在云函数根目录执行
npm install axios。
四、小程序端服务封装
4.1 REST API 封装
javascript
// services/api.js
/**
* 调用云函数代理外部API
* @param {string} url 外部API地址
* @param {string} method 请求方法
* @param {object} data 请求参数
*/
async function callExternalApi(url, method = 'GET', data = {}) {
try {
const result = await wx.cloud.callFunction({
name: 'callExternalApi',
data: { url, method, data }
})
if (result.result.success) {
return result.result.data
} else {
throw new Error(result.result.error)
}
} catch (err) {
console.error('API call failed:', err)
throw err
}
}
// ========== 具体业务API示例 ==========
/**
* 获取天气信息(调用高德天气API)
*/
async function getWeather(cityCode) {
const apiKey = 'YOUR_AMAP_KEY' // 高德开放平台密钥
const url = `https://restapi.amap.com/v3/weather/weatherinfo`
return await callExternalApi(url, 'GET', {
key: apiKey,
city: cityCode
})
}
/**
* 调用物流查询API
*/
async function queryExpress(no, type = 'auto') {
const url = 'https://api.kuaidi100.com/poll/query.do'
return await callExternalApi(url, 'POST', {
compat: true,
sign: '', // 需要按快递100文档计算签名
param: JSON.stringify({
number: no,
type,
resultv2: 1
})
})
}
/**
* 调用AI服务(示例:腾讯混元)
*/
async function callAI(prompt, sessionId) {
// 注意:实际项目中应使用云开发环境的云函数调用AI
// 此处仅示例外部API调用模式
const token = 'YOUR_AI_TOKEN'
const url = 'https://hunyuan.cloud.tencent.com/hyllm/v1/chatCompletions'
return await callExternalApi(url, 'POST', {
headers: {
'Authorization': `Bearer ${token}`
},
data: {
model: 'hunyuan',
messages: [{ role: 'user', content: prompt }],
stream: false
}
})
}
module.exports = {
getWeather,
queryExpress,
callAI
}
4.2 云数据库封装
javascript
// services/cloudDb.js
const db = wx.cloud.database()
/**
* 通用查询方法
*/
async function query(collectionName, where = {}, options = {}) {
const { limit = 20, skip = 0, orderBy = '_createTime', desc = true } = options
return await db.collection(collectionName)
.where(where)
.orderBy(orderBy, desc ? 'desc' : 'asc')
.skip(skip)
.limit(limit)
.get()
}
/**
* 通用新增方法
*/
async function add(collectionName, data) {
return await db.collection(collectionName).add({ data })
}
/**
* 通用更新方法
*/
async function update(collectionName, id, data) {
return await db.collection(collectionName).doc(id).update({ data })
}
/**
* 通用删除方法
*/
async function remove(collectionName, id) {
return await db.collection(collectionName).doc(id).remove()
}
module.exports = { query, add, update, remove }
五、页面中混合调用示例
javascript
// pages/order-detail/order-detail.js
const api = require('../../services/api.js')
const cloudDb = require('../../services/cloudDb.js')
Page({
data: {
orderInfo: null,
expressInfo: null,
weather: null
},
async onLoad(options) {
const { orderId } = options
// 并行调用:本地云数据库 + 外部物流API + 外部天气API
const [orderResult, expressResult, weatherResult] = await Promise.allSettled([
this.getOrderFromDb(orderId), // 云数据库
this.getExpressInfo(orderId), // 外部REST API
api.getWeather('南宁') // 外部REST API
])
// 处理结果
this.setData({
orderInfo: orderResult.status === 'fulfilled' ? orderResult.value : null,
expressInfo: expressResult.status === 'fulfilled' ? expressResult.value : null,
weather: weatherResult.status === 'fulfilled' ? weatherResult.value : null
})
},
// 从云数据库获取订单
async getOrderFromDb(orderId) {
const result = await cloudDb.query('orders', { _id: orderId }, { limit: 1 })
return result.data[0]
},
// 从外部API获取物流信息
async getExpressInfo(orderId) {
// 假设订单数据中包含快递单号
const order = await cloudDb.query('orders', { _id: orderId }, { limit: 1 })
const expressNo = order.data[0]?.expressNo
if (!expressNo) return null
return await api.queryExpress(expressNo)
}
})
六、安全注意事项
6.1 敏感信息不要放在小程序端
- API密钥不要硬编码在小程序代码中
- 云函数的环境变量中存储敏感配置
javascript
// 云函数中读取环境变量
const secretKey = process.env.SECRET_KEY // 通过云开发环境变量配置
6.2 接口限流与防护
javascript
// 云函数中添加简单的限流逻辑
const rateLimit = new Map() // 可改用Redis
async function checkRateLimit(openId) {
const now = Date.now()
const key = `rate:${openId}`
const lastTime = rateLimit.get(key) || 0
if (now - lastTime < 1000) { // 1秒内最多1次
throw new Error('请求过于频繁')
}
rateLimit.set(key, now)
}
6.3 数据校验
javascript
// 云函数入口处校验参数
function validateParams(event) {
const { url } = event
if (!url || typeof url !== 'string') {
throw new Error('url参数必填且为字符串')
}
// 校验是否为合法域名
const allowedDomains = ['api.kuaidi100.com', 'restapi.amap.com']
const urlObj = new URL(url)
if (!allowedDomains.includes(urlObj.hostname)) {
throw new Error('不支持的API域名')
}
}
七、部署与调试
7.1 本地调试云函数
bash
# 进入云函数目录
cd cloudfunctions/callExternalApi
# 初始化npm并安装依赖
npm init -y
npm install axios wx-server-sdk
# 使用微信开发者工具的云函数本地调试功能
7.2 云函数上传
bash
# 在项目根目录执行
miniprogram/
├── cloudbaserc.json # 云开发配置文件
└── cloudfunctions/
└── callExternalApi/
├── index.js
├── package.json
└── node_modules/ # 需要一起上传
提示:大型项目建议使用CI/CD自动部署云函数,配合GitHub Actions实现代码提交自动发布。
**本文作者:优睿科技(技术团队)