1. 云函数开发
1. 云函数的基本概念和结构
云函数的概念
云函数(Cloud Functions)是运行在云端的 JavaScript 代码,它是 uniCloud 的核心组件之一。云函数可以处理业务逻辑,访问云数据库,调用云存储,以及与其他云服务交互。与传统的服务器相比,云函数具有以下特点:
- 无服务器:开发者无需管理服务器,只需关注业务逻辑的实现。
- 按需执行:云函数只在被调用时才会执行,节省资源。
- 自动扩缩容:根据调用量自动扩缩容,无需手动管理。
- 事件驱动:可以通过各种事件触发,如 HTTP 请求、定时任务、数据库变更等。
云函数的基本结构
一个典型的云函数包含以下文件:
- index.js:云函数的主入口文件,包含云函数的实现代码。
- package.json:云函数的配置文件,包含依赖项、版本号等信息。
- 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 版本:
- Node.js 12:适用于较老的项目。
- Node.js 14:适用于大多数项目。
- Node.js 16:适用于新项目,支持更多新特性。
云函数的调用方式
云函数可以通过以下方式调用:
- 客户端调用:通过 uniCloud.callFunction 方法调用。
- 云函数间调用:通过 uniCloud.callFunction 方法调用其他云函数。
- HTTP 调用:通过 HTTP 请求调用云函数。
- 定时触发:通过定时触发器调用云函数。
- 数据库触发:通过数据库变更事件触发云函数。
2. 云函数的创建和部署
创建云函数
通过 HBuilderX 创建
- 在 HBuilderX 中,打开项目。
- 在项目的 cloudfunctions 目录下,右键点击。
- 选择"新建云函数"。
- 输入云函数名称,点击"创建"。
通过控制台创建
- 登录 uniCloud 控制台。
- 选择云服务空间。
- 点击"云函数"。
- 点击"创建云函数"。
- 输入云函数名称和配置信息,点击"创建"。
编写云函数代码
基本结构
'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 文件中,可以配置以下内容:
- 内存配置:设置云函数的内存大小,如 128MB、256MB 等。
- 超时时间:设置云函数的超时时间,如 5 秒、10 秒等。
- 环境变量:设置云函数的环境变量,如 API 密钥、数据库连接字符串等。
{
"cloudfunction-config": {
"memorySize": 256,
"timeout": 10,
"env": {
"API_KEY": "your-api-key"
}
}
}
触发器配置
在 package.json 文件中,可以配置以下触发器:
- HTTP 触发器:配置 HTTP 触发器,设置路径、方法、认证方式等。
- 定时触发器:配置定时触发器,设置触发时间、触发周期等。
- 数据库触发器:配置数据库触发器,设置集合、操作类型、过滤条件等。
{
"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 部署
- 在 HBuilderX 中,右键点击云函数。
- 选择"上传部署"。
- 等待部署完成。
通过控制台部署
- 登录 uniCloud 控制台。
- 选择云服务空间。
- 点击"云函数"。
- 选择要部署的云函数。
- 点击"上传"。
- 选择要上传的文件,点击"上传"。
通过命令行部署
-
安装 uniCloud 命令行工具:
npm install -g @dcloudio/unicloud-cli -
登录 uniCloud:
unicloud login -
部署云函数:
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'
}
};
}
};
日志查看
通过控制台查看
- 登录 uniCloud 控制台。
- 选择云服务空间。
- 点击"云函数"。
- 选择云函数。
- 点击"日志"。
- 查看日志内容。
通过 HBuilderX 查看
- 在 HBuilderX 中,右键点击云函数。
- 选择"查看日志"。
- 查看日志内容。
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'
}
};
}
};
云函数性能优化
减少冷启动时间
- 减少依赖项:只引入必要的依赖项,减少 node_modules 的大小。
- 优化代码:减少不必要的计算和操作,优化代码结构。
- 使用缓存:使用内存缓存或 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 等
}
优化内存使用
- 减少内存分配:减少不必要的对象创建和数组操作。
- 使用流式处理:对于大数据集,使用流式处理而不是一次性加载所有数据。
- 及时释放资源:使用完资源后及时释放,避免内存泄漏。
'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) {
// 处理文档的具体实现
}
优化网络请求
- 减少请求次数:合并多个请求为一个请求,减少网络往返。
- 使用批量操作:使用批量操作代替单个操作,减少数据库请求。
- 使用连接池:对于外部服务,使用连接池减少连接建立的开销。
'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'
};
};