Chapter 07

网络与数据

掌握 wx.request 封装、云开发 CloudBase 全栈开发与本地缓存数据策略

7.1 wx.request 基础

wx.request 是小程序发起 HTTP 网络请求的基础 API,支持 GET/POST/PUT/DELETE 等方法。有几个重要限制需要了解:

⚠️
域名白名单限制

小程序只能请求在微信公众平台后台配置了合法域名的服务器(开发模式下可临时关闭验证)。进入「开发管理 → 开发设置 → 服务器域名」,将你的 API 域名添加到 request 合法域名列表。域名必须是 HTTPS,不支持 HTTP(除 localhost 调试)。

封装 request 工具类

// utils/request.js — 生产可用的请求封装
const BASE_URL = 'https://api.example.com'
const TIMEOUT = 10000

// 将 wx.request 包装为 Promise
function request(options) {
  return new Promise((resolve, reject) => {
    const app = getApp()
    const token = app.globalData.token

    wx.request({
      url: BASE_URL + options.url,
      method: options.method || 'GET',
      data: options.data,
      timeout: TIMEOUT,
      header: {
        'Content-Type': 'application/json',
        'Authorization': token ? `Bearer ${token}` : '',
        ...options.header
      },
      success(res) {
        if (res.statusCode === 200) {
          const data = res.data
          if (data.code === 0) {
            resolve(data.data)
          } else if (data.code === 401) {
            // Token 过期,跳转登录
            wx.redirectTo({ url: '/pages/login/login' })
            reject(new Error('未登录'))
          } else {
            reject(new Error(data.message || '请求失败'))
          }
        } else {
          reject(new Error(`HTTP ${res.statusCode}`))
        }
      },
      fail(err) {
        if (err.errMsg.includes('timeout')) {
          reject(new Error('网络超时,请重试'))
        } else {
          reject(new Error('网络异常,请检查连接'))
        }
      }
    })
  })
}

// 便捷方法
const http = {
  get:    (url, data, header) => request({ url, data, header, method: 'GET' }),
  post:   (url, data, header) => request({ url, data, header, method: 'POST' }),
  put:    (url, data, header) => request({ url, data, header, method: 'PUT' }),
  delete: (url, data, header) => request({ url, data, header, method: 'DELETE' })
}

module.exports = http

// 使用示例
const http = require('../../utils/request')

async fetchProducts() {
  try {
    const data = await http.get('/products', { page: 1, size: 20 })
    this.setData({ products: data.items })
  } catch (e) {
    wx.showToast({ title: e.message, icon: 'error' })
  }
}

7.2 云开发 CloudBase

云开发(CloudBase)是腾讯为小程序提供的 Serverless 后端服务,开发者无需购买服务器,直接在前端调用云数据库、云存储和云函数。

初始化云开发

// app.js
App({
  onLaunch() {
    wx.cloud.init({
      env: 'your-env-id',    // 云开发环境 ID(在云开发控制台查看)
      traceUser: true        // 是否将用户访问记录到用户管理
    })
  }
})

云数据库操作

// 获取数据库引用
const db = wx.cloud.database()
const _ = db.command     // 查询/更新操作符

// ─── 查询 ───
// 查询单条
const product = await db.collection('products')
  .doc('product-id-123')
  .get()
console.log(product.data)

// 条件查询(带过滤、排序、分页)
const result = await db.collection('products')
  .where({
    category: 'electronics',
    price: _.lt(1000),      // price < 1000
    stock: _.gt(0)          // stock > 0
  })
  .orderBy('createdAt', 'desc')
  .skip((page - 1) * pageSize)
  .limit(pageSize)
  .get()

// 聚合查询
const stats = await db.collection('orders')
  .aggregate()
  .match({ userId: 'current-user-id' })
  .group({
    _id: '$status',
    count: $.sum(1),
    total: $.sum('$amount')
  })
  .end()

// ─── 新增 ───
const addRes = await db.collection('comments').add({
  data: {
    content: '很好的商品!',
    productId: 'product-id-123',
    createdAt: db.serverDate()   // 使用服务器时间
  }
})

// ─── 更新 ───
await db.collection('products').doc('product-id-123').update({
  data: {
    stock: _.inc(-1),        // 原子减 1(防并发)
    viewCount: _.inc(1),
    tags: _.push(['hot'])  // 向数组追加元素
  }
})

// ─── 删除 ───
await db.collection('comments').doc('comment-id').remove()

云存储

// 上传文件
const uploadImage = async () => {
  // 1. 选择图片
  const chooseRes = await wx.chooseMedia({
    count: 1,
    mediaType: ['image'],
    sizeType: ['compressed']
  })
  const tempFilePath = chooseRes.tempFiles[0].tempFilePath

  // 2. 上传到云存储
  const cloudPath = `avatars/${Date.now()}.jpg`
  const uploadRes = await wx.cloud.uploadFile({
    cloudPath,
    filePath: tempFilePath
  })

  // 3. 获取访问 URL
  const { fileList } = await wx.cloud.getTempFileURL({
    fileList: [uploadRes.fileID]
  })
  return fileList[0].tempFileURL
}

云函数

// cloudfunctions/getOpenId/index.js(服务端代码)
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })

exports.main = async (event, context) => {
  const { OPENID, UNIONID } = cloud.getWXContext()
  return { openId: OPENID, unionId: UNIONID }
}

// 小程序端调用云函数
const res = await wx.cloud.callFunction({
  name: 'getOpenId',
  data: { /* 传给云函数的参数 */ }
})
console.log('openId:', res.result.openId)

7.3 本地缓存策略

// 同步存储(少量数据,会阻塞)
wx.setStorageSync('userInfo', userInfo)
const cached = wx.getStorageSync('userInfo')

// 异步存储(推荐,不阻塞逻辑层)
await wx.setStorage({ key: 'token', data: 'jwt...' })
const { data: token } = await wx.getStorage({ key: 'token' })

// 带过期时间的缓存封装
const cache = {
  set(key, data, ttlSeconds = 3600) {
    wx.setStorageSync(key, {
      data,
      expireAt: Date.now() + ttlSeconds * 1000
    })
  },
  get(key) {
    try {
      const item = wx.getStorageSync(key)
      if (!item) return null
      if (item.expireAt < Date.now()) {
        wx.removeStorageSync(key)
        return null
      }
      return item.data
    } catch {
      return null
    }
  }
}

// 使用:先读缓存,缓存失效再请求
async getCategories() {
  const cached = cache.get('categories')
  if (cached) return cached

  const data = await http.get('/categories')
  cache.set('categories', data, 60 * 60)  // 缓存 1 小时
  return data
}
ℹ️
缓存容量限制

单个 key 存储上限为 1 MB,单个小程序总存储上限为 10 MB。存储大量数据(如图片列表)时要注意控制大小,定期清理过期缓存。可用 wx.getStorageInfoSync() 查看当前已用空间。