微信小程序云开发环境搭建与REST API混合架构实战

微信小程序云开发环境搭建与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实现代码提交自动发布。


**本文作者:优睿科技(技术团队)

相关推荐
Greg_Zhong2 小时前
解决绘制的雷达图在页面有滚动时,雷达图出现`轻微上下偏移`的问题
微信小程序·canvans绘制雷达图
空中海2 小时前
微信小程序 - 02 基础概念层与核心能力层
微信小程序·小程序
無名路人4 小时前
小程序点餐页吸顶滚动
前端·微信小程序·ai编程
游戏开发爱好者85 小时前
使用Fiddler设置HTTPS抓包诊断Power Query网络问题
android·ios·小程序·https·uni-app·iphone·webview
七月的冰红茶5 小时前
【开发工具】使用cursor实现点单小程序
小程序
Greg_Zhong6 小时前
微信小程序中使用canvas实现雷达图及标签对角显示(实现雷达图标签的标准做法)
微信小程序·小程序canvas实现雷达图·标签不通过canvas绘制
码农客栈7 小时前
小程序学习(十八)之“骨架屏”
小程序
棋宣8 小时前
uni-app编译到微信小程序中,父传子props首次传递数据不接收的bug
微信小程序·uni-app·bug
kyh10033811209 小时前
微信小程序摇骰子功能实现|含源码
微信小程序·小程序·摇骰子小游戏·摇色子源码