uniCloud 云函数开发

164 阅读12分钟

1. 云函数开发

1. 云函数的基本概念和结构

云函数的概念

云函数(Cloud Functions)是运行在云端的 JavaScript 代码,它是 uniCloud 的核心组件之一。云函数可以处理业务逻辑,访问云数据库,调用云存储,以及与其他云服务交互。与传统的服务器相比,云函数具有以下特点:

  1. 无服务器:开发者无需管理服务器,只需关注业务逻辑的实现。
  2. 按需执行:云函数只在被调用时才会执行,节省资源。
  3. 自动扩缩容:根据调用量自动扩缩容,无需手动管理。
  4. 事件驱动:可以通过各种事件触发,如 HTTP 请求、定时任务、数据库变更等。

云函数的基本结构

一个典型的云函数包含以下文件:

  1. index.js:云函数的主入口文件,包含云函数的实现代码。
  2. package.json:云函数的配置文件,包含依赖项、版本号等信息。
  3. node_modules:云函数的依赖包目录。
index.js 的基本结构
'use strict';
exports.main = async (event, context) => {
  // 云函数入口函数
  // event 参数包含了调用云函数时传入的参数
  // context 参数包含了云函数的上下文信息
  
  // 业务逻辑实现
  
  // 返回结果
  return {
    // 返回的数据
  };
};
package.json 的基本结构
{
  "name": "function-name",
  "version": "1.0.0",
  "description": "云函数描述",
  "main": "index.js",
  "dependencies": {
    // 依赖项
  },
  "extensions": {
		// 云函数使用的扩展库
	},
  "cloudfunction-config": {
    "memorySize": 256,
    "timeout": 10,
    "triggers": [{
				"name": "myTrigger",
				"type": "timer",
				"config": "0 0 2 1 * * *"
		}],
    "path": "/function-path",
    "runtime": "Nodejs8"
  }
}
cloudfunction-config

其中cloudfunction-config字段是云函数配置,支持的配置如下:

{
  "concurrency": 10, // 单个云函数实例最大并发量,不配置的情况下默认是1
  "memorySize": 512, // 函数的最大可用内存,单位MB,可选值: 128|256|512|1024|2048,默认值256,阿里云正式版默认512
  "timeout": 60, // 函数的超时时间,单位秒,默认值5。阿里云最长为120秒,阿里云在定时触发时最长可以是7200秒
  // triggers 字段是触发器数组,目前仅支持一个触发器,即数组只能填写一个,不可添加多个
  "triggers": [{ // 阿里云腾讯云均为此形式,请阅读下方说明
      // name: 触发器的名字,规则见https://uniapp.dcloud.net.cn/uniCloud/trigger,name不对阿里云生效
      "name": "myTrigger",
      // type: 触发器类型,目前仅支持 timer (即 定时触发器),type不对阿里云生效
      "type": "timer",
      // config: 触发器配置,在定时触发器下,config 格式为 cron 表达式,规则见https://uniapp.dcloud.net.cn/uniCloud/trigger。使用阿里云时会自动忽略最后一位,即代表年份的一位在阿里云不生效
      "config": "0 0 2 1 * * *"
  }],
  // 云函数Url化path部分
  "path": "",
  "runtime": "", // nodejs版本,可选Nodejs8、Nodejs12、Nodejs14、Nodejs16、Nodejs18、Nodejs20
  "keepRunningAfterReturn": true // 是否在云函数return之后继续执行,仅腾讯云nodejs12生效,详情见下方说明
}

云函数的执行环境

云函数运行在 Node.js 环境中,支持以下 Node.js 版本:

  1. Node.js 12:适用于较老的项目。
  2. Node.js 14:适用于大多数项目。
  3. Node.js 16:适用于新项目,支持更多新特性。

云函数的调用方式

云函数可以通过以下方式调用:

  1. 客户端调用:通过 uniCloud.callFunction 方法调用。
  2. 云函数间调用:通过 uniCloud.callFunction 方法调用其他云函数。
  3. HTTP 调用:通过 HTTP 请求调用云函数。
  4. 定时触发:通过定时触发器调用云函数。
  5. 数据库触发:通过数据库变更事件触发云函数。

2. 云函数的创建和部署

创建云函数

通过 HBuilderX 创建
  1. 在 HBuilderX 中,打开项目。
  2. 在项目的 cloudfunctions 目录下,右键点击。
  3. 选择"新建云函数"。
  4. 输入云函数名称,点击"创建"。
通过控制台创建
  1. 登录 uniCloud 控制台。
  2. 选择云服务空间。
  3. 点击"云函数"。
  4. 点击"创建云函数"。
  5. 输入云函数名称和配置信息,点击"创建"。

编写云函数代码

基本结构
'use strict';
exports.main = async (event, context) => {
  // 云函数入口函数
  const { name, age } = event;
  
  // 业务逻辑实现
  const result = {
    name,
    age,
    message: `Hello, ${name}! You are ${age} years old.`
  };
  
  // 返回结果
  return result;
};
访问云数据库
'use strict';
exports.main = async (event, context) => {
  // 云函数入口函数
  const db = uniCloud.database();
  
  // 查询数据
  const collection = db.collection('users');
  const result = await collection.where({
    age: event.age
  }).get();
  
  // 返回结果
  return {
    data: result.data
  };
};
访问云存储
'use strict';
exports.main = async (event, context) => {
  // 云函数入口函数
  const { fileID } = event;
  
  // 获取文件信息
  const result = await uniCloud.getTempFileURL({
    fileList: [fileID]
  });
  
  // 返回结果
  return {
    fileURL: result.fileList[0].tempFileURL
  };
};

配置云函数

基本配置

在 package.json 文件中,可以配置以下内容:

  1. 内存配置:设置云函数的内存大小,如 128MB、256MB 等。
  2. 超时时间:设置云函数的超时时间,如 5 秒、10 秒等。
  3. 环境变量:设置云函数的环境变量,如 API 密钥、数据库连接字符串等。
{
  "cloudfunction-config": {
    "memorySize": 256,
    "timeout": 10,
    "env": {
      "API_KEY": "your-api-key"
    }
  }
}
触发器配置

在 package.json 文件中,可以配置以下触发器:

  1. HTTP 触发器:配置 HTTP 触发器,设置路径、方法、认证方式等。
  2. 定时触发器:配置定时触发器,设置触发时间、触发周期等。
  3. 数据库触发器:配置数据库触发器,设置集合、操作类型、过滤条件等。
{
  "cloudfunction-config": {
    "triggers": [
      {
        "name": "httpTrigger",
        "type": "http",
        "config": {
          "path": "/api/users",
          "method": "GET",
          "auth": "none"
        }
      },
      {
        "name": "timerTrigger",
        "type": "timer",
        "config": {
          "cron": "0 0 * * * *",
          "name": "dailyTask"
        }
      }
    ]
  }
}

部署云函数

通过 HBuilderX 部署
  1. 在 HBuilderX 中,右键点击云函数。
  2. 选择"上传部署"。
  3. 等待部署完成。
通过控制台部署
  1. 登录 uniCloud 控制台。
  2. 选择云服务空间。
  3. 点击"云函数"。
  4. 选择要部署的云函数。
  5. 点击"上传"。
  6. 选择要上传的文件,点击"上传"。
通过命令行部署
  1. 安装 uniCloud 命令行工具:

    npm install -g @dcloudio/unicloud-cli
    
  2. 登录 uniCloud:

    unicloud login
    
  3. 部署云函数:

    unicloud deploy --function function-name
    

3. 云函数的调用和参数传递

客户端调用云函数

基本调用
// 调用云函数
uniCloud.callFunction({
  name: 'function-name',
  data: {
    name: 'John',
    age: 30
  }
}).then(res => {
  console.log(res.result);
}).catch(err => {
  console.error(err);
});
异步调用
// 异步调用云函数
async function callCloudFunction() {
  try {
    const res = await uniCloud.callFunction({
      name: 'function-name',
      data: {
        name: 'John',
        age: 30
      }
    });
    console.log(res.result);
  } catch (err) {
    console.error(err);
  }
}

云函数间调用

基本调用
'use strict';
exports.main = async (event, context) => {
  // 调用其他云函数
  const res = await uniCloud.callFunction({
    name: 'another-function',
    data: {
      param1: 'value1',
      param2: 'value2'
    }
  });
  
  // 处理结果
  return {
    result: res.result
  };
};
异步调用
'use strict';
exports.main = async (event, context) => {
  try {
    // 调用其他云函数
    const res = await uniCloud.callFunction({
      name: 'another-function',
      data: {
        param1: 'value1',
        param2: 'value2'
      }
    });
    
    // 处理结果
    return {
      result: res.result
    };
  } catch (err) {
    // 处理错误
    return {
      error: err.message
    };
  }
};

HTTP 调用云函数

配置 HTTP 触发器

在 package.json 文件中,配置 HTTP 触发器:

{
  "cloudfunction-config": {
    "triggers": [
      {
        "name": "httpTrigger",
        "type": "http",
        "config": {
          "path": "/api/users",
          "method": "GET",
          "auth": "none"
        }
      }
    ]
  }
}
调用 HTTP 触发器
// 调用 HTTP 触发器
fetch('https://your-api-endpoint/api/users')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

参数传递

基本参数
// 客户端传递参数
uniCloud.callFunction({
  name: 'function-name',
  data: {
    name: 'John',
    age: 30
  }
});

// 云函数接收参数
exports.main = async (event, context) => {
  const { name, age } = event;
  // 使用参数
};
复杂参数
// 客户端传递复杂参数
uniCloud.callFunction({
  name: 'function-name',
  data: {
    user: {
      name: 'John',
      age: 30,
      address: {
        city: 'New York',
        country: 'USA'
      }
    },
    options: {
      includeDetails: true,
      limit: 10
    }
  }
});

// 云函数接收复杂参数
exports.main = async (event, context) => {
  const { user, options } = event;
  // 使用参数
};
文件参数
// 客户端传递文件参数
uniCloud.callFunction({
  name: 'function-name',
  data: {
    fileID: 'cloud://xxx.xxx/file.jpg'
  }
});

// 云函数接收文件参数
exports.main = async (event, context) => {
  const { fileID } = event;
  // 使用文件参数
};

4. 云函数的错误处理和日志

错误处理

基本错误处理
'use strict';
exports.main = async (event, context) => {
  try {
    // 业务逻辑
    const result = await someAsyncOperation();
    return {
      success: true,
      data: result
    };
  } catch (error) {
    // 错误处理
    return {
      success: false,
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR'
      }
    };
  }
};
自定义错误
'use strict';
// 自定义错误类
class CustomError extends Error {
  constructor(message, code) {
    super(message);
    this.name = 'CustomError';
    this.code = code;
  }
}

exports.main = async (event, context) => {
  try {
    // 业务逻辑
    if (!event.name) {
      throw new CustomError('Name is required', 'INVALID_PARAMETER');
    }
    
    const result = await someAsyncOperation();
    return {
      success: true,
      data: result
    };
  } catch (error) {
    // 错误处理
    if (error instanceof CustomError) {
      return {
        success: false,
        error: {
          message: error.message,
          code: error.code
        }
      };
    } else {
      return {
        success: false,
        error: {
          message: error.message,
          code: 'UNKNOWN_ERROR'
        }
      };
    }
  }
};

日志记录

基本日志
'use strict';
exports.main = async (event, context) => {
  // 记录信息日志
  console.log('Function started', event);
  
  try {
    // 业务逻辑
    const result = await someAsyncOperation();
    
    // 记录成功日志
    console.log('Function completed successfully', result);
    
    return {
      success: true,
      data: result
    };
  } catch (error) {
    // 记录错误日志
    console.error('Function failed', error);
    
    return {
      success: false,
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR'
      }
    };
  }
};
结构化日志
'use strict';
exports.main = async (event, context) => {
  // 记录结构化日志
  console.log(JSON.stringify({
    level: 'info',
    message: 'Function started',
    event,
    timestamp: new Date().toISOString()
  }));
  
  try {
    // 业务逻辑
    const result = await someAsyncOperation();
    
    // 记录结构化成功日志
    console.log(JSON.stringify({
      level: 'info',
      message: 'Function completed successfully',
      result,
      timestamp: new Date().toISOString()
    }));
    
    return {
      success: true,
      data: result
    };
  } catch (error) {
    // 记录结构化错误日志
    console.error(JSON.stringify({
      level: 'error',
      message: 'Function failed',
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR',
        stack: error.stack
      },
      timestamp: new Date().toISOString()
    }));
    
    return {
      success: false,
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR'
      }
    };
  }
};

日志查看

通过控制台查看
  1. 登录 uniCloud 控制台。
  2. 选择云服务空间。
  3. 点击"云函数"。
  4. 选择云函数。
  5. 点击"日志"。
  6. 查看日志内容。
通过 HBuilderX 查看
  1. 在 HBuilderX 中,右键点击云函数。
  2. 选择"查看日志"。
  3. 查看日志内容。

5. 云函数的高级特性

云函数中间件

基本中间件
'use strict';
// 中间件函数
async function middleware(event, context) {
  // 前置处理
  console.log('Before processing', event);
  
  // 调用下一个中间件或主函数
  const result = await next(event, context);
  
  // 后置处理
  console.log('After processing', result);
  
  return result;
}

// 主函数
async function main(event, context) {
  // 业务逻辑
  return {
    message: 'Hello, World!'
  };
}

// 导出函数
exports.main = async (event, context) => {
  // 应用中间件
  return middleware(event, context, main);
};
多个中间件
'use strict';
// 中间件数组
const middlewares = [
  async (event, context, next) => {
    console.log('Middleware 1 - Before');
    const result = await next(event, context);
    console.log('Middleware 1 - After');
    return result;
  },
  async (event, context, next) => {
    console.log('Middleware 2 - Before');
    const result = await next(event, context);
    console.log('Middleware 2 - After');
    return result;
  }
];

// 主函数
async function main(event, context) {
  // 业务逻辑
  return {
    message: 'Hello, World!'
  };
}

// 应用中间件
async function applyMiddlewares(event, context, middlewares, main) {
  let index = 0;
  
  async function next(event, context) {
    if (index < middlewares.length) {
      const middleware = middlewares[index++];
      return middleware(event, context, next);
    } else {
      return main(event, context);
    }
  }
  
  return next(event, context);
}

// 导出函数
exports.main = async (event, context) => {
  // 应用中间件
  return applyMiddlewares(event, context, middlewares, main);
};

云函数定时触发器

配置定时触发器

在 package.json 文件中,配置定时触发器:

{
  "cloudfunction-config": {
    "triggers": [
      {
        "name": "timerTrigger",
        "type": "timer",
        "config": {
          "cron": "0 0 * * * *",
          "name": "dailyTask"
        }
      }
    ]
  }
}
实现定时任务
'use strict';
exports.main = async (event, context) => {
  // 定时任务逻辑
  console.log('Daily task started', new Date().toISOString());
  
  try {
    // 执行定时任务
    await performDailyTask();
    
    console.log('Daily task completed successfully', new Date().toISOString());
    return {
      success: true,
      message: 'Daily task completed successfully'
    };
  } catch (error) {
    console.error('Daily task failed', error);
    return {
      success: false,
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR'
      }
    };
  }
};

// 执行定时任务
async function performDailyTask() {
  // 定时任务的具体实现
  // 例如:数据备份、数据清理、数据统计等
}

云函数数据库触发器

配置数据库触发器

在 package.json 文件中,配置数据库触发器:

{
  "cloudfunction-config": {
    "triggers": [
      {
        "name": "dbTrigger",
        "type": "database",
        "config": {
          "collection": "users",
          "operation": ["insert", "update", "delete"],
          "condition": "doc.age > 18"
        }
      }
    ]
  }
}
实现数据库触发器
'use strict';
exports.main = async (event, context) => {
  // 数据库触发器逻辑
  console.log('Database trigger started', event);
  
  try {
    // 处理数据库事件
    const { operation, collection, doc, updatedDoc } = event;
    
    switch (operation) {
      case 'insert':
        await handleInsert(collection, doc);
        break;
      case 'update':
        await handleUpdate(collection, doc, updatedDoc);
        break;
      case 'delete':
        await handleDelete(collection, doc);
        break;
      default:
        console.warn('Unknown operation', operation);
    }
    
    console.log('Database trigger completed successfully', new Date().toISOString());
    return {
      success: true,
      message: 'Database trigger completed successfully'
    };
  } catch (error) {
    console.error('Database trigger failed', error);
    return {
      success: false,
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR'
      }
    };
  }
};

// 处理插入事件
async function handleInsert(collection, doc) {
  // 处理插入事件的具体实现
  // 例如:发送通知、更新统计信息等
}

// 处理更新事件
async function handleUpdate(collection, doc, updatedDoc) {
  // 处理更新事件的具体实现
  // 例如:记录变更历史、更新缓存等
}

// 处理删除事件
async function handleDelete(collection, doc) {
  // 处理删除事件的具体实现
  // 例如:清理关联数据、更新统计信息等
}

云函数并发控制

使用锁控制并发
'use strict';
exports.main = async (event, context) => {
  const db = uniCloud.database();
  const lockCollection = db.collection('locks');
  
  try {
    // 尝试获取锁
    const lockResult = await lockCollection.where({
      resource: 'resource-name',
      locked: false
    }).update({
      locked: true,
      lockedBy: context.FUNCTION_NAME,
      lockedAt: new Date()
    });
    
    if (lockResult.updated === 0) {
      // 锁已被其他云函数获取
      return {
        success: false,
        error: {
          message: 'Resource is locked',
          code: 'RESOURCE_LOCKED'
        }
      };
    }
    
    // 获取锁成功,执行需要并发控制的操作
    await performConcurrentOperation();
    
    // 释放锁
    await lockCollection.where({
      resource: 'resource-name',
      lockedBy: context.FUNCTION_NAME
    }).update({
      locked: false,
      lockedBy: null,
      lockedAt: null
    });
    
    return {
      success: true,
      message: 'Operation completed successfully'
    };
  } catch (error) {
    // 发生错误,确保释放锁
    try {
      await lockCollection.where({
        resource: 'resource-name',
        lockedBy: context.FUNCTION_NAME
      }).update({
        locked: false,
        lockedBy: null,
        lockedAt: null
      });
    } catch (releaseError) {
      console.error('Failed to release lock', releaseError);
    }
    
    return {
      success: false,
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR'
      }
    };
  }
};

// 执行需要并发控制的操作
async function performConcurrentOperation() {
  // 需要并发控制的操作的具体实现
}
使用事务控制并发
'use strict';
exports.main = async (event, context) => {
  const db = uniCloud.database();
  
  // 开始事务
  const transaction = await db.startTransaction();
  
  try {
    // 在事务中执行操作
    const collection = transaction.collection('users');
    
    // 查询数据
    const user = await collection.doc(event.userId).get();
    
    // 更新数据
    await collection.doc(event.userId).update({
      balance: user.data[0].balance - event.amount
    });
    
    // 提交事务
    await transaction.commit();
    
    return {
      success: true,
      message: 'Transaction completed successfully'
    };
  } catch (error) {
    // 回滚事务
    await transaction.rollback();
    
    return {
      success: false,
      error: {
        message: error.message,
        code: error.code || 'UNKNOWN_ERROR'
      }
    };
  }
};

云函数性能优化

减少冷启动时间
  1. 减少依赖项:只引入必要的依赖项,减少 node_modules 的大小。
  2. 优化代码:减少不必要的计算和操作,优化代码结构。
  3. 使用缓存:使用内存缓存或 Redis 缓存,减少重复计算和数据库查询。
'use strict';
// 内存缓存
const cache = new Map();

exports.main = async (event, context) => {
  const { key } = event;
  
  // 检查缓存
  if (cache.has(key)) {
    return {
      success: true,
      data: cache.get(key),
      fromCache: true
    };
  }
  
  // 缓存未命中,执行操作
  const data = await fetchData(key);
  
  // 更新缓存
  cache.set(key, data);
  
  return {
    success: true,
    data,
    fromCache: false
  };
};

// 获取数据
async function fetchData(key) {
  // 获取数据的具体实现
  // 例如:查询数据库、调用外部 API 等
}
优化内存使用
  1. 减少内存分配:减少不必要的对象创建和数组操作。
  2. 使用流式处理:对于大数据集,使用流式处理而不是一次性加载所有数据。
  3. 及时释放资源:使用完资源后及时释放,避免内存泄漏。
'use strict';
exports.main = async (event, context) => {
  const db = uniCloud.database();
  const collection = db.collection('large-collection');
  
  // 使用流式处理
  const cursor = collection.where({
    status: 'pending'
  }).limit(1000).get();
  
  const results = [];
  
  // 分批处理数据
  for await (const doc of cursor) {
    // 处理单个文档
    const processedDoc = await processDoc(doc);
    results.push(processedDoc);
    
    // 每处理 100 个文档,返回一次结果
    if (results.length % 100 === 0) {
      // 可以在这里返回部分结果,或者继续处理
    }
  }
  
  return {
    success: true,
    data: results
  };
};

// 处理文档
async function processDoc(doc) {
  // 处理文档的具体实现
}
优化网络请求
  1. 减少请求次数:合并多个请求为一个请求,减少网络往返。
  2. 使用批量操作:使用批量操作代替单个操作,减少数据库请求。
  3. 使用连接池:对于外部服务,使用连接池减少连接建立的开销。
'use strict';
exports.main = async (event, context) => {
  const db = uniCloud.database();
  const collection = db.collection('users');
  
  // 批量查询
  const userIds = event.userIds;
  const users = await collection.where({
    _id: db.command.in(userIds)
  }).get();
  
  // 批量更新
  const updates = users.data.map(user => ({
    _id: user._id,
    lastLoginAt: new Date()
  }));
  
  await collection.batchUpdate(updates);
  
  return {
    success: true,
    message: 'Batch operation completed successfully'
  };
};