网络请求与云开发
💡 核心结论
wx.request发送HTTP请求,域名需要在后台配置白名单
Promise封装wx.request可以使用async/await简化异步代码
云开发提供数据库、存储、云函数,无需搭建服务器
云数据库支持权限控制和实时推送
小程序登录通过code换取openid和session_key
1. 网络请求
1.1 wx.request基础
//pages/index/index.js
Page({
data: {
list: []
},
onLoad() {
this.loadData()
},
loadData() {
wx.showLoading({ title: '加载中' })
wx.request({
url: 'https://api.example.com/products',
method: 'GET',
data: {
page: 1,
size: 10
},
header: {
'content-type': 'application/json',
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
timeout: 10000,
success: (res) => {
if (res.statusCode === 200) {
this.setData({
list: res.data.list
})
} else {
wx.showToast({
title: '请求失败',
icon: 'none'
})
}
},
fail: (err) => {
console.error('请求失败', err)
wx.showToast({
title: '网络错误',
icon: 'none'
})
},
complete: () => {
wx.hideLoading()
}
})
}
})
1.2 Promise封装
// utils/request.js
const baseURL = 'https://api.example.com'
function request(options) {
return new Promise((resolve, reject) => {
wx.request({
url: baseURL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'content-type': 'application/json',
'Authorization': 'Bearer ' + wx.getStorageSync('token'),
...options.header
},
success: (res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data)
} else {
reject(res)
}
},
fail: (err) => {
reject(err)
}
})
})
}
// 封装常用方法
export const http = {
get(url, data = {}) {
return request({ url, method: 'GET', data })
},
post(url, data = {}) {
return request({ url, method: 'POST', data })
},
put(url, data = {}) {
return request({ url, method: 'PUT', data })
},
delete(url, data = {}) {
return request({ url, method: 'DELETE', data })
}
}
1.3 使用async/await
// pages/index/index.js
import { http } from '../../utils/request.js'
Page({
data: {
products: [],
page: 1
},
async onLoad() {
await this.loadProducts()
},
async loadProducts() {
try {
wx.showLoading({ title: '加载中' })
const data = await http.get('/products', {
page: this.data.page,
size: 10
})
this.setData({
products: data.list
})
} catch (error) {
console.error(error)
wx.showToast({
title: '加载失败',
icon: 'none'
})
} finally {
wx.hideLoading()
}
},
// 上拉加载更多
async onReachBottom() {
this.setData({
page: this.data.page + 1
})
try {
const data = await http.get('/products', {
page: this.data.page,
size: 10
})
this.setData({
products: [...this.data.products, ...data.list]
})
} catch (error) {
console.error(error)
}
}
})
1.4 请求拦截器
// utils/request.js
const baseURL = 'https://api.example.com'
// 请求拦截器
function requestInterceptor(options) {
// 添加token
const token = wx.getStorageSync('token')
if (token) {
options.header = {
...options.header,
'Authorization': 'Bearer ' + token
}
}
// 添加时间戳(防止缓存)
if (options.method === 'GET') {
options.data = {
...options.data,
_t: Date.now()
}
}
return options
}
// 响应拦截器
function responseInterceptor(res) {
// 统一处理响应
if (res.statusCode === 200) {
// 业务成功
if (res.data.code === 0) {
return res.data
}
// 业务失败
else {
// token过期,重新登录
if (res.data.code === 401) {
wx.removeStorageSync('token')
wx.reLaunch({
url: '/pages/login/login'
})
return Promise.reject(new Error('登录已过期'))
}
// 其他业务错误
wx.showToast({
title: res.data.message || '请求失败',
icon: 'none'
})
return Promise.reject(new Error(res.data.message))
}
}
// HTTP错误
else {
wx.showToast({
title: '网络错误',
icon: 'none'
})
return Promise.reject(new Error('网络错误'))
}
}
function request(options) {
// 应用请求拦截器
options = requestInterceptor(options)
return new Promise((resolve, reject) => {
wx.request({
url: baseURL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'content-type': 'application/json',
...options.header
},
success: (res) => {
// 应用响应拦截器
responseInterceptor(res)
.then(resolve)
.catch(reject)
},
fail: (err) => {
wx.showToast({
title: '网络错误',
icon: 'none'
})
reject(err)
}
})
})
}
export const http = {
get(url, data = {}) {
return request({ url, method: 'GET', data })
},
post(url, data = {}) {
return request({ url, method: 'POST', data })
}
}
1.5 错误处理
// utils/errorHandler.js
export const errorHandler = {
// 处理网络错误
handleNetworkError(error) {
if (error.errMsg.includes('timeout')) {
wx.showToast({
title: '请求超时',
icon: 'none'
})
} else if (error.errMsg.includes('fail')) {
wx.showToast({
title: '网络连接失败',
icon: 'none'
})
} else {
wx.showToast({
title: '网络错误',
icon: 'none'
})
}
},
// 处理业务错误
handleBusinessError(code, message) {
switch (code) {
case 400:
wx.showToast({
title: message || '请求参数错误',
icon: 'none'
})
break
case 401:
wx.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
})
setTimeout(() => {
wx.reLaunch({
url: '/pages/login/login'
})
}, 1500)
break
case 403:
wx.showToast({
title: '没有权限',
icon: 'none'
})
break
case 404:
wx.showToast({
title: '资源不存在',
icon: 'none'
})
break
case 500:
wx.showToast({
title: '服务器错误',
icon: 'none'
})
break
default:
wx.showToast({
title: message || '操作失败',
icon: 'none'
})
}
}
}
// 使用
import { errorHandler } from '../../utils/errorHandler.js'
Page({
async loadData() {
try {
const data = await http.get('/products')
this.setData({ products: data.list })
} catch (error) {
if (error.errMsg) {
errorHandler.handleNetworkError(error)
} else {
errorHandler.handleBusinessError(error.code, error.message)
}
}
}
})
1.6 API接口管理
// api/product.js
import { http } from '../utils/request.js'
export const productAPI = {
// 获取商品列表
getList(params) {
return http.get('/products', params)
},
// 获取商品详情
getDetail(id) {
return http.get(`/products/${id}`)
},
// 创建商品
create(data) {
return http.post('/products', data)
},
// 更新商品
update(id, data) {
return http.put(`/products/${id}`, data)
},
// 删除商品
delete(id) {
return http.delete(`/products/${id}`)
}
}
// 使用
import { productAPI } from '../../api/product.js'
Page({
async loadProducts() {
try {
const data = await productAPI.getList({ page: 1, size: 10 })
this.setData({ products: data.list })
} catch (error) {
console.error('加载商品失败', error)
}
}
})
2. 用户登录
2.1 微信登录流程
// app.js
App({
globalData: {
userInfo: null,
token: ''
},
onLaunch() {
this.login()
},
// 登录
async login() {
try {
// 1. 获取code
const loginRes = await wx.login()
const code = loginRes.code
// 2. 发送code到后端
const res = await wx.request({
url: 'https://api.example.com/login',
method: 'POST',
data: { code }
})
// 3. 保存token
this.globalData.token = res.data.token
wx.setStorageSync('token', res.data.token)
// 4. 获取用户信息
await this.getUserProfile()
} catch (error) {
console.error('登录失败', error)
}
},
// 获取用户信息(需要用户授权)
async getUserProfile() {
try {
const res = await wx.getUserProfile({
desc: '用于完善会员资料'
})
this.globalData.userInfo = res.userInfo
wx.setStorageSync('userInfo', res.userInfo)
// 发送到后端
await wx.request({
url: 'https://api.example.com/user/profile',
method: 'POST',
data: res.userInfo,
header: {
'Authorization': 'Bearer ' + this.globalData.token
}
})
} catch (error) {
console.error('获取用户信息失败', error)
}
}
})
2.2 后端接口(Node.js示例)
// 后端:login接口
const axios = require('axios')
const jwt = require('jsonwebtoken')
app.post('/login', async (req, res) => {
const { code } = req.body
// 1. 用code换取openid和session_key
const wechatRes = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
params: {
appid: 'your_appid',
secret: 'your_secret',
js_code: code,
grant_type: 'authorization_code'
}
})
const { openid, session_key } = wechatRes.data
// 2. 查询或创建用户
let user = await User.findOne({ openid })
if (!user) {
user = await User.create({ openid })
}
// 3. 生成JWT token
const token = jwt.sign(
{ userId: user.id, openid },
'your_secret_key',
{ expiresIn: '7d' }
)
res.json({ token, userId: user.id })
})
3. 云开发
3.1 初始化云开发
// app.js
App({
onLaunch() {
// 初始化云开发
wx.cloud.init({
env: 'your-env-id', // 云开发环境ID
traceUser: true
})
}
})
3.2 云数据库
创建数据:
// pages/add/add.js
Page({
async onAdd() {
const db = wx.cloud.database()
try {
const res = await db.collection('todos').add({
data: {
title: '学习小程序',
done: false,
createTime: db.serverDate() // 服务器时间
}
})
console.log('添加成功', res._id)
wx.showToast({ title: '添加成功' })
} catch (error) {
console.error('添加失败', error)
}
}
})
查询数据:
Page({
async loadTodos() {
const db = wx.cloud.database()
const _ = db.command
try {
// 简单查询
const res = await db.collection('todos')
.where({
done: false
})
.orderBy('createTime', 'desc')
.limit(20)
.get()
this.setData({
todos: res.data
})
// 条件查询
const res2 = await db.collection('todos')
.where({
// 等于
status: 'active',
// 大于
score: _.gt(60),
// 包含
tags: _.in(['work', 'study'])
})
.get()
// 正则匹配
const res3 = await db.collection('todos')
.where({
title: db.RegExp({
regexp: '学习',
options: 'i' // 不区分大小写
})
})
.get()
} catch (error) {
console.error('查询失败', error)
}
}
})
更新数据:
Page({
async updateTodo(id) {
const db = wx.cloud.database()
try {
await db.collection('todos').doc(id).update({
data: {
done: true,
updateTime: db.serverDate()
}
})
wx.showToast({ title: '更新成功' })
} catch (error) {
console.error('更新失败', error)
}
},
// 数组操作
async addTag(id) {
const db = wx.cloud.database()
const _ = db.command
await db.collection('todos').doc(id).update({
data: {
tags: _.push(['new-tag']) // 添加到数组
}
})
}
})
删除数据:
Page({
async deleteTodo(id) {
const db = wx.cloud.database()
try {
await db.collection('todos').doc(id).remove()
wx.showToast({ title: '删除成功' })
} catch (error) {
console.error('删除失败', error)
}
}
})
3.3 云存储
Page({
// 上传图片
async uploadImage() {
try {
// 选择图片
const chooseRes = await wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera']
})
const filePath = chooseRes.tempFilePaths[0]
// 上传到云存储
const uploadRes = await wx.cloud.uploadFile({
cloudPath: `images/${Date.now()}-${Math.random()}.png`,
filePath: filePath
})
console.log('上传成功', uploadRes.fileID)
// 获取临时链接
const tempURLRes = await wx.cloud.getTempFileURL({
fileList: [uploadRes.fileID]
})
const imageURL = tempURLRes.fileList[0].tempFileURL
this.setData({ imageURL })
} catch (error) {
console.error('上传失败', error)
}
},
// 下载文件
async downloadFile(fileID) {
try {
const res = await wx.cloud.downloadFile({
fileID: fileID
})
console.log('临时文件路径', res.tempFilePath)
} catch (error) {
console.error('下载失败', error)
}
},
// 删除文件
async deleteFile(fileID) {
try {
await wx.cloud.deleteFile({
fileList: [fileID]
})
wx.showToast({ title: '删除成功' })
} catch (error) {
console.error('删除失败', error)
}
}
})
3.4 云函数
创建云函数:
// cloudfunctions/getProducts/index.js
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
const { page = 1, size = 10 } = event
try {
// 可以突破小程序端20条的限制
const res = await db.collection('products')
.skip((page - 1) * size)
.limit(size)
.orderBy('createTime', 'desc')
.get()
return {
code: 0,
data: res.data,
total: res.data.length
}
} catch (error) {
return {
code: -1,
error: error.message
}
}
}
调用云函数:
// pages/index/index.js
Page({
async loadProducts() {
try {
wx.showLoading({ title: '加载中' })
const res = await wx.cloud.callFunction({
name: 'getProducts',
data: {
page: 1,
size: 20
}
})
if (res.result.code === 0) {
this.setData({
products: res.result.data
})
}
} catch (error) {
console.error('调用失败', error)
} finally {
wx.hideLoading()
}
}
})
4. WebSocket
// pages/chat/chat.js
Page({
data: {
messages: [],
socketOpen: false
},
onLoad() {
this.connectWebSocket()
},
onUnload() {
this.closeWebSocket()
},
// 连接WebSocket
connectWebSocket() {
wx.connectSocket({
url: 'wss://example.com/ws',
header: {
'Authorization': 'Bearer ' + wx.getStorageSync('token')
}
})
// 连接成功
wx.onSocketOpen(() => {
console.log('WebSocket连接已打开')
this.setData({ socketOpen: true })
// 发送心跳
this.startHeartbeat()
})
// 接收消息
wx.onSocketMessage((res) => {
const message = JSON.parse(res.data)
this.setData({
messages: [...this.data.messages, message]
})
})
// 连接关闭
wx.onSocketClose(() => {
console.log('WebSocket连接已关闭')
this.setData({ socketOpen: false })
// 重连
setTimeout(() => {
this.connectWebSocket()
}, 3000)
})
// 错误
wx.onSocketError((error) => {
console.error('WebSocket错误', error)
})
},
// 发送消息
sendMessage(content) {
if (!this.data.socketOpen) {
wx.showToast({
title: '连接已断开',
icon: 'none'
})
return
}
wx.sendSocketMessage({
data: JSON.stringify({
type: 'message',
content: content
})
})
},
// 心跳
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.data.socketOpen) {
wx.sendSocketMessage({
data: JSON.stringify({ type: 'ping' })
})
}
}, 30000) // 30秒
},
// 关闭连接
closeWebSocket() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
}
wx.closeSocket()
}
})
5. 文件操作
5.1 图片选择和上传
Page({
async chooseAndUpload() {
try {
// 选择图片
const chooseRes = await wx.chooseImage({
count: 9,
sizeType: ['compressed'],
sourceType: ['album', 'camera']
})
const tempFilePaths = chooseRes.tempFilePaths
// 批量上传
const uploadPromises = tempFilePaths.map(filePath => {
return wx.uploadFile({
url: 'https://api.example.com/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': 'Bearer ' + wx.getStorageSync('token')
}
})
})
const results = await Promise.all(uploadPromises)
const imageUrls = results.map(res => {
const data = JSON.parse(res.data)
return data.url
})
this.setData({ images: imageUrls })
wx.showToast({ title: '上传成功' })
} catch (error) {
console.error('上传失败', error)
}
}
})
5.2 图片预览
Page({
previewImage(current) {
wx.previewImage({
current: current,
urls: this.data.images
})
}
})
6. 常见问题
Q1: 如何配置合法域名?
A:
登录小程序后台
开发 → 开发管理 → 开发设置 → 服务器域名
配置request、uploadFile、downloadFile、socket域名
Q2: 云开发和传统后端的区别?
A:
云开发:无需搭建服务器,按量付费,快速开发
传统后端:自己搭建,成本高,灵活性强
Q3: 如何处理并发请求?
A:
Promise.all([
request1(),
request2(),
request3()
]).then(results => {
// 所有请求完成
})
参考资源
微信小程序网络API文档
云开发官方文档
WebSocket协议规范