uni-app 云函数开发

章节介绍

随着云计算技术的发展,云函数(Serverless)已经成为现代应用开发的重要组成部分。uni-app 提供了基于 uniCloud 的云函数开发能力,开发者可以通过云函数实现服务端逻辑,无需关心服务器的搭建和维护。本章节将详细介绍如何开发 uni-app 云函数,包括云函数的概念、创建方法、调用方式、部署流程和最佳实践,帮助开发者快速上手云函数开发。

核心知识点讲解

1. 云函数概述

云函数是一种无服务器计算服务,允许开发者在云端运行代码,无需关心服务器的搭建、配置和维护。在 uni-app 中,云函数基于 uniCloud 实现,具有以下特点:

  • 按需运行:云函数只在被调用时运行,无需长期占用服务器资源
  • 自动扩缩容:根据请求量自动调整计算资源,应对高并发场景
  • 无需运维:无需关心服务器的搭建、配置、监控和维护
  • 低成本:按实际使用量计费,避免资源浪费
  • 易于集成:与 uni-app 客户端无缝集成,调用简单

2. 云函数开发环境搭建

在开始云函数开发之前,需要做好以下准备工作:

2.1 注册并登录 DCloud 开发者账号

2.2 开通 uniCloud 服务

  • 在 DCloud 开发者中心中开通 uniCloud 服务
  • 创建服务空间(选择阿里云或腾讯云)
  • 记录服务空间 ID,用于后续配置

2.3 配置 HBuilderX

  • 下载并安装最新版本的 HBuilderX
  • 在 HBuilderX 中登录 DCloud 开发者账号
  • 关联服务空间:点击 "uniCloud" > "关联服务空间或项目",选择已创建的服务空间

3. 云函数的创建与配置

3.1 创建云函数

在 HBuilderX 中创建云函数的步骤如下:

  1. 在项目中创建云函数目录

    • 在 uni-app 项目中,点击 "uniCloud" > "初始化云开发环境"
    • 选择服务空间,生成 cloudfunctions 目录
  2. 创建云函数

    • cloudfunctions 目录上右键,选择 "新建云函数"
    • 输入云函数名称,如 "hello"
    • HBuilderX 会自动生成云函数的基本结构

3.2 云函数的基本结构

一个标准的云函数包含以下文件:

cloudfunctions/
└── hello/          # 云函数名称
    ├── index.js    # 云函数入口文件
    └── package.json # 云函数配置文件

index.js 文件是云函数的入口文件,示例代码如下:

'use strict';

exports.main = async (event, context) => {
  // event 包含客户端传递的参数
  console.log('event:', event);
  
  // context 包含运行上下文信息
  console.log('context:', context);
  
  // 返回结果给客户端
  return {
    code: 0,
    message: 'success',
    data: {
      hello: 'world'
    }
  };
};

package.json 文件是云函数的配置文件,示例代码如下:

{
  "name": "hello",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT"
}

4. 云函数的调用

在 uni-app 客户端中,调用云函数的方法如下:

4.1 调用云函数

uniCloud.callFunction({
  name: 'hello', // 云函数名称
  data: { // 传递给云函数的参数
    username: 'test',
    age: 18
  },
  success: (res) => {
    console.log('调用成功:', res.result);
  },
  fail: (err) => {
    console.error('调用失败:', err);
  }
});

4.2 使用 async/await 调用

async function callHello() {
  try {
    const res = await uniCloud.callFunction({
      name: 'hello',
      data: {
        username: 'test',
        age: 18
      }
    });
    console.log('调用成功:', res.result);
    return res.result;
  } catch (err) {
    console.error('调用失败:', err);
    throw err;
  }
}

// 调用函数
callHello();

5. 云函数的部署

5.1 本地调试

在部署云函数之前,可以先在本地进行调试:

  1. 配置本地调试环境
    • 点击 "uniCloud" > "云函数本地调试"
    • 选择要调试的云函数
    • 输入测试参数
    • 点击 "运行" 按钮查看执行结果

5.2 部署云函数

部署云函数到云端的步骤如下:

  1. 单个云函数部署

    • 在云函数目录上右键,选择 "上传部署"
    • 等待部署完成
  2. 批量部署

    • cloudfunctions 目录上右键,选择 "上传所有云函数"
    • 等待所有云函数部署完成
  3. 部署状态查看

    • 部署完成后,HBuilderX 会显示部署结果
    • 可以在 DCloud 开发者中心的云函数管理页面查看已部署的云函数

6. 云函数的高级特性

6.1 云函数的依赖管理

云函数可以使用 npm 包来扩展功能,步骤如下:

  1. 在云函数目录中初始化 npm

    • 在云函数目录上右键,选择 "在终端中打开"
    • 执行 npm init -y 命令初始化 package.json
  2. 安装依赖包

    • 执行 npm install 包名 命令安装依赖,如 npm install axios
  3. 在云函数中使用依赖

    • 在 index.js 文件中引入依赖,如 const axios = require('axios');
  4. 部署时自动打包依赖

    • 部署云函数时,HBuilderX 会自动打包依赖包
    • 确保依赖包的大小不超过云函数的限制(阿里云 50MB,腾讯云 100MB)

6.2 云函数的超时设置

云函数的执行时间有限制,默认超时时间为 3 秒(阿里云)或 10 秒(腾讯云)。可以在 package.json 文件中设置超时时间:

{
  "name": "hello",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "cloudfunction-config": {
    "timeout": 10 // 超时时间,单位秒
  }
}

6.3 云函数的环境变量

可以在 DCloud 开发者中心的云函数管理页面设置环境变量,用于存储敏感信息:

  1. 登录 DCloud 开发者中心
  2. 进入服务空间管理
  3. 点击 "云函数" > "配置" > "环境变量"
  4. 添加环境变量,如数据库连接字符串、API 密钥等

在云函数中使用环境变量:

'use strict';

exports.main = async (event, context) => {
  // 获取环境变量
  const apiKey = process.env.API_KEY;
  console.log('apiKey:', apiKey);
  
  // 使用环境变量
  // ...
  
  return {
    code: 0,
    message: 'success'
  };
};

7. 云函数与数据库操作

uniCloud 提供了内置的数据库服务,云函数可以直接操作数据库:

7.1 初始化数据库

在云函数中初始化数据库:

'use strict';
const db = uniCloud.database();

exports.main = async (event, context) => {
  // 数据库操作
  // ...
};

7.2 数据库操作示例

插入数据

'use strict';
const db = uniCloud.database();

exports.main = async (event, context) => {
  const { name, age } = event;
  
  // 插入数据
  const res = await db.collection('users').add({
    name,
    age,
    createTime: new Date()
  });
  
  return {
    code: 0,
    message: 'success',
    data: res
  };
};

查询数据

'use strict';
const db = uniCloud.database();

exports.main = async (event, context) => {
  const { age } = event;
  
  // 查询数据
  const res = await db.collection('users')
    .where({ age: { $gt: age } })
    .limit(10)
    .get();
  
  return {
    code: 0,
    message: 'success',
    data: res.data
  };
};

更新数据

'use strict';
const db = uniCloud.database();

exports.main = async (event, context) => {
  const { id, name } = event;
  
  // 更新数据
  const res = await db.collection('users')
    .doc(id)
    .update({
      name,
      updateTime: new Date()
    });
  
  return {
    code: 0,
    message: 'success',
    data: res
  };
};

删除数据

'use strict';
const db = uniCloud.database();

exports.main = async (event, context) => {
  const { id } = event;
  
  // 删除数据
  const res = await db.collection('users')
    .doc(id)
    .remove();
  
  return {
    code: 0,
    message: 'success',
    data: res
  };
};

8. 云函数的最佳实践

8.1 代码结构优化

  • 模块化:将复杂的云函数拆分为多个模块,提高代码可维护性
  • 错误处理:使用 try-catch 捕获错误,返回统一的错误格式
  • 日志记录:使用 console.log 记录关键操作,便于调试和排查问题
  • 参数校验:对客户端传递的参数进行校验,确保数据安全

8.2 性能优化

  • 减少冷启动时间

    • 减少依赖包的大小
    • 避免在初始化时执行耗时操作
    • 使用云函数的预留实例(付费功能)
  • 优化数据库操作

    • 使用索引加速查询
    • 避免全表扫描
    • 批量操作数据,减少数据库请求次数
  • 合理设置超时时间

    • 根据云函数的实际执行时间设置合理的超时时间
    • 避免设置过长的超时时间,导致资源浪费

8.3 安全最佳实践

  • 使用环境变量:将敏感信息存储在环境变量中,避免硬编码
  • 参数校验:对客户端传递的参数进行严格校验,防止注入攻击
  • 权限控制:使用 uniCloud 的权限控制功能,限制云函数的调用权限
  • 数据加密:对敏感数据进行加密存储和传输
  • 防止滥用:限制云函数的调用频率,防止恶意调用

实用案例分析

案例一:开发一个用户注册登录系统

需求分析

开发一个用户注册登录系统,包括用户注册、登录、获取用户信息等功能。

实现步骤

  1. 创建云函数

创建以下云函数:

  • register:用户注册
  • login:用户登录
  • getUserInfo:获取用户信息
  1. 实现用户注册云函数

register/index.js

'use strict';
const db = uniCloud.database();
const crypto = require('crypto');

exports.main = async (event, context) => {
  const { username, password, email } = event;
  
  // 参数校验
  if (!username || !password || !email) {
    return {
      code: 400,
      message: '参数错误,缺少必要字段'
    };
  }
  
  // 检查用户名是否已存在
  const userRes = await db.collection('users')
    .where({ username })
    .get();
  
  if (userRes.data.length > 0) {
    return {
      code: 400,
      message: '用户名已存在'
    };
  }
  
  // 检查邮箱是否已存在
  const emailRes = await db.collection('users')
    .where({ email })
    .get();
  
  if (emailRes.data.length > 0) {
    return {
      code: 400,
      message: '邮箱已存在'
    };
  }
  
  // 密码加密
  const hashPassword = crypto.createHash('md5').update(password).digest('hex');
  
  // 插入用户数据
  const res = await db.collection('users').add({
    username,
    password: hashPassword,
    email,
    createTime: new Date(),
    lastLoginTime: null
  });
  
  return {
    code: 0,
    message: '注册成功',
    data: {
      userId: res.id
    }
  };
};
  1. 实现用户登录云函数

login/index.js

'use strict';
const db = uniCloud.database();
const crypto = require('crypto');
const jwt = require('jsonwebtoken');

exports.main = async (event, context) => {
  const { username, password } = event;
  
  // 参数校验
  if (!username || !password) {
    return {
      code: 400,
      message: '参数错误,缺少用户名或密码'
    };
  }
  
  // 查找用户
  const userRes = await db.collection('users')
    .where({ username })
    .get();
  
  if (userRes.data.length === 0) {
    return {
      code: 401,
      message: '用户名或密码错误'
    };
  }
  
  const user = userRes.data[0];
  
  // 验证密码
  const hashPassword = crypto.createHash('md5').update(password).digest('hex');
  if (user.password !== hashPassword) {
    return {
      code: 401,
      message: '用户名或密码错误'
    };
  }
  
  // 生成 token
  const token = jwt.sign(
    { userId: user._id, username: user.username },
    'your-secret-key', // 实际使用时应存储在环境变量中
    { expiresIn: '7d' }
  );
  
  // 更新最后登录时间
  await db.collection('users')
    .doc(user._id)
    .update({
      lastLoginTime: new Date()
    });
  
  return {
    code: 0,
    message: '登录成功',
    data: {
      token,
      user: {
        _id: user._id,
        username: user.username,
        email: user.email
      }
    }
  };
};
  1. 实现获取用户信息云函数

getUserInfo/index.js

'use strict';
const db = uniCloud.database();
const jwt = require('jsonwebtoken');

exports.main = async (event, context) => {
  const { token } = event;
  
  // 参数校验
  if (!token) {
    return {
      code: 401,
      message: '未提供 token'
    };
  }
  
  try {
    // 验证 token
    const decoded = jwt.verify(token, 'your-secret-key'); // 实际使用时应存储在环境变量中
    const userId = decoded.userId;
    
    // 获取用户信息
    const userRes = await db.collection('users')
      .doc(userId)
      .field({
        password: false // 不返回密码字段
      })
      .get();
    
    if (userRes.data.length === 0) {
      return {
        code: 404,
        message: '用户不存在'
      };
    }
    
    return {
      code: 0,
      message: '获取成功',
      data: {
        user: userRes.data[0]
      }
    };
  } catch (error) {
    return {
      code: 401,
      message: 'token 无效或已过期'
    };
  }
};
  1. 在客户端调用云函数

注册

async function register() {
  try {
    const res = await uniCloud.callFunction({
      name: 'register',
      data: {
        username: 'test',
        password: '123456',
        email: 'test@example.com'
      }
    });
    
    if (res.result.code === 0) {
      console.log('注册成功:', res.result.data);
      // 处理注册成功后的逻辑
    } else {
      console.error('注册失败:', res.result.message);
      // 处理注册失败后的逻辑
    }
  } catch (error) {
    console.error('调用云函数失败:', error);
  }
}

登录

async function login() {
  try {
    const res = await uniCloud.callFunction({
      name: 'login',
      data: {
        username: 'test',
        password: '123456'
      }
    });
    
    if (res.result.code === 0) {
      console.log('登录成功:', res.result.data);
      // 保存 token
      uni.setStorageSync('token', res.result.data.token);
      // 处理登录成功后的逻辑
    } else {
      console.error('登录失败:', res.result.message);
      // 处理登录失败后的逻辑
    }
  } catch (error) {
    console.error('调用云函数失败:', error);
  }
}

获取用户信息

async function getUserInfo() {
  try {
    const token = uni.getStorageSync('token');
    const res = await uniCloud.callFunction({
      name: 'getUserInfo',
      data: {
        token
      }
    });
    
    if (res.result.code === 0) {
      console.log('获取用户信息成功:', res.result.data.user);
      // 处理获取用户信息成功后的逻辑
    } else {
      console.error('获取用户信息失败:', res.result.message);
      // 处理获取用户信息失败后的逻辑
    }
  } catch (error) {
    console.error('调用云函数失败:', error);
  }
}

案例二:开发一个文件上传系统

需求分析

开发一个文件上传系统,包括文件上传、获取文件列表、删除文件等功能。

实现步骤

  1. 创建云函数

创建以下云函数:

  • uploadFile:文件上传(使用 uniCloud 的文件存储功能)
  • getFileList:获取文件列表
  • deleteFile:删除文件
  1. 实现文件上传功能

注意:文件上传通常使用 uniCloud 的客户端上传 API,而不是云函数。这里我们使用云函数来处理上传后的逻辑,如记录文件信息到数据库。

客户端上传代码

async function uploadFile() {
  try {
    // 选择文件
    const [chooseResult] = await uni.chooseImage({
      count: 1,
      sizeType: ['original', 'compressed'],
      sourceType: ['album', 'camera']
    });
    
    // 上传文件到 uniCloud
    const uploadResult = await uniCloud.uploadFile({
      filePath: chooseResult.tempFilePaths[0],
      cloudPath: 'images/' + Date.now() + '.jpg'
    });
    
    if (uploadResult.success) {
      console.log('文件上传成功:', uploadResult.fileID);
      
      // 调用云函数记录文件信息
      const recordResult = await uniCloud.callFunction({
        name: 'recordFile',
        data: {
          fileID: uploadResult.fileID,
          fileName: chooseResult.tempFiles[0].name,
          fileSize: chooseResult.tempFiles[0].size,
          fileType: chooseResult.tempFiles[0].type
        }
      });
      
      if (recordResult.result.code === 0) {
        console.log('文件记录成功:', recordResult.result.data);
      } else {
        console.error('文件记录失败:', recordResult.result.message);
      }
    } else {
      console.error('文件上传失败:', uploadResult.errMsg);
    }
  } catch (error) {
    console.error('上传失败:', error);
  }
}

recordFile/index.js

'use strict';
const db = uniCloud.database();

exports.main = async (event, context) => {
  const { fileID, fileName, fileSize, fileType } = event;
  
  // 参数校验
  if (!fileID) {
    return {
      code: 400,
      message: '缺少 fileID 参数'
    };
  }
  
  // 记录文件信息
  const res = await db.collection('files').add({
    fileID,
    fileName,
    fileSize,
    fileType,
    uploadTime: new Date()
  });
  
  return {
    code: 0,
    message: '记录成功',
    data: {
      id: res.id
    }
  };
};
  1. 实现获取文件列表云函数

getFileList/index.js

'use strict';
const db = uniCloud.database();

exports.main = async (event, context) => {
  const { page = 1, pageSize = 10 } = event;
  
  // 计算偏移量
  const offset = (page - 1) * pageSize;
  
  // 获取文件列表
  const res = await db.collection('files')
    .orderBy('uploadTime', 'desc')
    .skip(offset)
    .limit(pageSize)
    .get();
  
  // 获取总记录数
  const countRes = await db.collection('files').count();
  
  return {
    code: 0,
    message: '获取成功',
    data: {
      files: res.data,
      total: countRes.total,
      page,
      pageSize
    }
  };
};
  1. 实现删除文件云函数

deleteFile/index.js

'use strict';
const db = uniCloud.database();
const cloud = uniCloud;

exports.main = async (event, context) => {
  const { id, fileID } = event;
  
  // 参数校验
  if (!id || !fileID) {
    return {
      code: 400,
      message: '缺少必要参数'
    };
  }
  
  try {
    // 从 uniCloud 存储中删除文件
    await cloud.deleteFile({
      fileList: [fileID]
    });
    
    // 从数据库中删除文件记录
    await db.collection('files').doc(id).remove();
    
    return {
      code: 0,
      message: '删除成功'
    };
  } catch (error) {
    console.error('删除文件失败:', error);
    return {
      code: 500,
      message: '删除失败'
    };
  }
};
  1. 在客户端调用云函数

获取文件列表

async function getFileList() {
  try {
    const res = await uniCloud.callFunction({
      name: 'getFileList',
      data: {
        page: 1,
        pageSize: 10
      }
    });
    
    if (res.result.code === 0) {
      console.log('获取文件列表成功:', res.result.data);
      // 处理获取文件列表成功后的逻辑
    } else {
      console.error('获取文件列表失败:', res.result.message);
      // 处理获取文件列表失败后的逻辑
    }
  } catch (error) {
    console.error('调用云函数失败:', error);
  }
}

删除文件

async function deleteFile(id, fileID) {
  try {
    const res = await uniCloud.callFunction({
      name: 'deleteFile',
      data: {
        id,
        fileID
      }
    });
    
    if (res.result.code === 0) {
      console.log('删除文件成功');
      // 处理删除文件成功后的逻辑
    } else {
      console.error('删除文件失败:', res.result.message);
      // 处理删除文件失败后的逻辑
    }
  } catch (error) {
    console.error('调用云函数失败:', error);
  }
}

学习目标

通过本章节的学习,您应该能够:

  1. 了解云函数的概念和特点
  2. 掌握云函数的创建和配置方法
  3. 学会在客户端调用云函数
  4. 掌握云函数的部署流程
  5. 了解云函数的依赖管理
  6. 掌握云函数操作数据库的方法
  7. 了解云函数的高级特性和最佳实践
  8. 能够开发实际的云函数应用,如用户系统和文件上传系统

总结与展望

云函数是 uni-app 开发中的重要组成部分,它为开发者提供了一种简单、高效的服务端开发方式,无需关心服务器的搭建和维护。通过本章节的学习,您已经掌握了云函数的创建、配置、调用、部署和最佳实践,为您开发服务端逻辑奠定了基础。

在后续的学习中,我们将继续探索 uni-app 的高级特性,包括云函数与前端的结合使用、云函数的性能优化、云函数的安全防护等内容,帮助您成为一名更加全面的 uni-app 开发者。

记住,云函数是一种强大的工具,但也需要合理使用。在开发过程中,要注意代码的质量和性能,遵循最佳实践,确保云函数的稳定运行和安全可靠。

« 上一篇 uni-app 插件发布与管理 下一篇 » uni-app 云数据库操作