HarmonyOS开发:云函数Cloud Functions
HarmonyOS开发:云函数Cloud Functions
📌 核心要点:Cloud Functions让你不用管服务器就能跑后端逻辑,事件触发、HTTP调用、定时任务,写完代码直接部署,按调用次数付费。
背景与动机
你写了个HarmonyOS应用,需要后端接口。怎么办?
方案一:自己搭服务器。买机器、装系统、配Nginx、写接口、搞CI/CD、做监控……等你搞完,黄花菜都凉了,而且一个人根本维护不过来。
方案二:用BaaS。认证、数据库、存储都有现成服务了,但总有些逻辑必须在服务端跑——比如校验支付签名、聚合多个数据源、处理敏感操作。这些逻辑放客户端不安全,放服务器又太重。
方案三:Cloud Functions。写个函数,部署到云端,自动扩缩容,按调用次数付费。不用管服务器,不用管运维,专注写业务逻辑就行。
你可能会说:这不就是AWS Lambda吗?对,概念一样,但Cloud Functions和AGC的其他服务(Cloud DB、Cloud Storage、Push等)深度集成,触发器配置开箱即用,不需要你自己对接各种SDK。
核心原理
Cloud Functions的本质是:你只写函数,平台负责运行环境、扩缩容、触发器、日志。
flowchart TD
A[触发源] --> B{触发类型}
B -->|HTTP触发| C[客户端直接调用]
B -->|Cloud DB触发| D[数据变更事件]
B -->|Cloud Storage触发| E[文件上传事件]
B -->|Auth触发| F[用户注册/登录事件]
B -->|定时触发| G[Cron定时任务]
B -->|Push触发| H[推送回调事件]
C --> I[Cloud Functions运行时]
D --> I
E --> I
F --> I
G --> I
H --> I
I --> J{函数执行}
J --> K[访问Cloud DB]
J --> L[访问Cloud Storage]
J --> M[调用第三方API]
J --> N[发送Push消息]
K --> O[返回结果]
L --> O
M --> O
N --> O
O --> P[日志与监控]
classDef trigger fill:#E65100,color:#fff,stroke:#BF360C
classDef runtime fill:#1565C0,color:#fff,stroke:#0D47A1
classDef service fill:#2E7D32,color:#fff,stroke:#1B5E20
classDef output fill:#6A1B9A,color:#fff,stroke:#4A148C
class A,B,C,D,E,F,G,H,trigger
class I,J,runtime
class K,L,M,N,service
class O,P,output
触发器机制
触发器是Cloud Functions的核心概念。函数不是"一直在跑",而是"被触发时才跑"。触发器定义了"什么事件会触发这个函数"。
| 触发器类型 | 适用场景 | 示例 |
|---|---|---|
| HTTP | 客户端主动调用 | 查询订单、提交表单 |
| Cloud DB | 数据变更时触发 | 新订单通知、数据同步 |
| Cloud Storage | 文件上传时触发 | 图片压缩、视频转码 |
| Auth | 用户注册/登录时触发 | 发送欢迎邮件、初始化用户数据 |
| Cron | 定时执行 | 数据清理、报表生成 |
| Push | 推送回调 | 推送送达回执处理 |
冷启动问题
Cloud Functions是按需创建实例的。一个函数长时间没被调用,实例会被回收。下次调用时需要重新创建实例,这个过程叫"冷启动",通常需要几百毫秒到几秒。
冷启动不是bug,是Serverless的固有特性。你需要做的是:优化函数体积、减少初始化逻辑、关键路径避免冷启动。
代码实战
基础用法:编写和部署HTTP触发函数
先写一个最简单的HTTP函数,然后在客户端调用它。
// === 云函数代码(部署到AGC) ===
// 函数名: getArticleDetail
// 触发器: HTTP
// index.js - 云函数入口
const cloud = require('@agconnect/cloud-server');
// 获取Cloud DB实例
const db = cloud.database();
exports.handler = async (event, context) => {
// 从HTTP请求中获取参数
const articleId = event.queryStringParameters?.id;
if (!articleId) {
return {
statusCode: 400,
body: JSON.stringify({ code: -1, message: '缺少文章ID' })
};
}
try {
// 从Cloud DB查询文章
const article = await db.collection('articles')
.doc(articleId)
.get();
if (!article) {
return {
statusCode: 404,
body: JSON.stringify({ code: -2, message: '文章不存在' })
};
}
// 返回文章数据
return {
statusCode: 200,
body: JSON.stringify({
code: 0,
data: article,
message: 'success'
})
};
} catch (error) {
console.error('查询文章失败:', error);
return {
statusCode: 500,
body: JSON.stringify({ code: -3, message: '服务器内部错误' })
};
}
};
// === 客户端调用代码 ===
// CloudFunctionClient.ets
import { cloudFunctions } from '@kit.CloudFunctionsKit';
export class CloudFunctionClient {
/**
* 调用云函数
* 最基础的调用方式
*/
static async callFunction(functionName: string, params: Record<string, Object>): Promise<Record<string, Object> | null> {
try {
const result = await cloudFunctions.call({
name: functionName, // 函数名
data: params // 传入参数
});
if (result && result.result) {
const response = JSON.parse(result.result as string);
if (response.code === 0) {
return response.data;
} else {
console.error(`[CloudFunc] 业务错误: ${response.message}`);
return null;
}
}
return null;
} catch (error) {
console.error(`[CloudFunc] 调用失败: ${JSON.stringify(error)}`);
return null;
}
}
/**
* 获取文章详情
*/
static async getArticleDetail(articleId: string): Promise<Record<string, Object> | null> {
return CloudFunctionClient.callFunction('getArticleDetail', { id: articleId });
}
}
进阶用法:Cloud DB触发器与定时任务
HTTP触发器是"你调它才跑",而Cloud DB触发器和定时任务是"事件驱动,自动执行"。
// === 云函数:Cloud DB触发器 ===
// 函数名: onOrderCreated
// 触发器: Cloud DB - orders集合 - onCreate事件
const cloud = require('@agconnect/cloud-server');
const db = cloud.database();
const push = cloud.push();
exports.handler = async (event, context) => {
// event包含变更的数据
const orderId = event.data.id;
const userId = event.data.userId;
const totalAmount = event.data.totalAmount;
console.log(`新订单创建: orderId=${orderId}, userId=${userId}, amount=${totalAmount}`);
try {
// 1. 更新用户订单计数
await db.collection('users').doc(userId).update({
orderCount: cloud.db.command.inc(1),
lastOrderTime: new Date().toISOString()
});
// 2. 发送订单确认Push消息
await push.sendMessage({
target: { userId: userId },
title: '订单创建成功',
body: `您的订单${orderId}已创建,金额¥${totalAmount}`,
data: { type: 'order_created', orderId: orderId }
});
// 3. 如果是大额订单,额外通知运营
if (totalAmount > 1000) {
await push.sendMessage({
target: { topic: 'vip_orders' },
title: '大额订单提醒',
body: `用户${userId}下单¥${totalAmount}`,
data: { type: 'vip_order', orderId: orderId }
});
}
return { code: 0, message: '处理成功' };
} catch (error) {
console.error('订单处理失败:', error);
// 抛出异常会触发重试
throw error;
}
};
// === 云函数:定时触发器 ===
// 函数名: dailyReport
// 触发器: Cron - 每天8:00执行 (0 8 * * *)
const cloud = require('@agconnect/cloud-server');
const db = cloud.database();
exports.handler = async (event, context) => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const dateStr = yesterday.toISOString().split('T')[0];
console.log(`开始生成${dateStr}的日报`);
try {
// 1. 统计昨日的注册用户数
const newUsers = await db.collection('users')
.where({ createdAt: cloud.db.command.gte(yesterday.toISOString()) })
.count();
// 2. 统计昨日的订单数和金额
const orders = await db.collection('orders')
.where({ createdAt: cloud.db.command.gte(yesterday.toISOString()) })
.get();
const orderCount = orders.length;
const totalRevenue = orders.reduce((sum, order) => sum + order.totalAmount, 0);
// 3. 写入日报表
await db.collection('daily_reports').add({
date: dateStr,
newUsers: newUsers,
orderCount: orderCount,
totalRevenue: totalRevenue,
avgOrderAmount: orderCount > 0 ? totalRevenue / orderCount : 0,
generatedAt: new Date().toISOString()
});
console.log(`日报生成完成: 新用户${newUsers}, 订单${orderCount}, 收入¥${totalRevenue}`);
return { code: 0, message: '日报生成成功' };
} catch (error) {
console.error('日报生成失败:', error);
throw error;
}
};
完整示例:客户端集成云函数的完整方案
实际项目中,调用云函数不是简单的一个call就完了。你得处理超时、重试、并发控制、错误分类。
// CloudFunctionService.ets
import { cloudFunctions } from '@kit.CloudFunctionsKit';
// 错误类型枚举
enum FunctionErrorType {
NETWORK_ERROR = 'network', // 网络错误,可重试
TIMEOUT_ERROR = 'timeout', // 超时,可重试
BUSINESS_ERROR = 'business', // 业务错误,不可重试
RATE_LIMIT_ERROR = 'rate_limit', // 限流,延迟重试
SERVER_ERROR = 'server' // 服务端错误,可重试
}
// 函数调用结果
interface FunctionResult<T> {
success: boolean;
data: T | null;
errorType: FunctionErrorType | null;
errorMessage: string;
retryCount: number;
}
// 函数调用配置
interface FunctionConfig {
timeout: number; // 超时时间(毫秒)
maxRetries: number; // 最大重试次数
retryDelay: number; // 重试间隔(毫秒)
cacheTtl: number; // 缓存时间(毫秒),0表示不缓存
}
// 默认配置
const DEFAULT_CONFIG: FunctionConfig = {
timeout: 10000,
maxRetries: 2,
retryDelay: 1000,
cacheTtl: 0
};
export class CloudFunctionService {
// 请求缓存
private static cache: Map<string, { data: Object; expireAt: number }> = new Map();
// 并发控制:同一函数同时只允许一个请求
private static pendingRequests: Map<string, Promise<FunctionResult<Object>>> = new Map();
/**
* 调用云函数(带重试、超时、缓存)
*/
static async call<T>(
functionName: string,
params: Record<string, Object>,
config: Partial<FunctionConfig> = {}
): Promise<FunctionResult<T>> {
const finalConfig = { ...DEFAULT_CONFIG, ...config };
const cacheKey = `${functionName}:${JSON.stringify(params)}`;
// 1. 检查缓存
if (finalConfig.cacheTtl > 0) {
const cached = CloudFunctionService.getFromCache<T>(cacheKey);
if (cached) {
console.info(`[CloudFunc] 命中缓存: ${functionName}`);
return { success: true, data: cached, errorType: null, errorMessage: '', retryCount: 0 };
}
}
// 2. 并发控制:复用相同请求
if (CloudFunctionService.pendingRequests.has(cacheKey)) {
console.info(`[CloudFunc] 复用请求: ${functionName}`);
const result = await CloudFunctionService.pendingRequests.get(cacheKey)!;
return result as FunctionResult<T>;
}
// 3. 带重试的调用
const promise = CloudFunctionService.callWithRetry<T>(
functionName, params, finalConfig
);
CloudFunctionService.pendingRequests.set(cacheKey, promise as Promise<FunctionResult<Object>>);
try {
const result = await promise;
// 4. 成功时写入缓存
if (result.success && result.data && finalConfig.cacheTtl > 0) {
CloudFunctionService.setToCache(cacheKey, result.data, finalConfig.cacheTtl);
}
return result;
} finally {
CloudFunctionService.pendingRequests.delete(cacheKey);
}
}
/**
* 带重试的函数调用
*/
private static async callWithRetry<T>(
functionName: string,
params: Record<string, Object>,
config: FunctionConfig
): Promise<FunctionResult<T>> {
let lastError: Error | null = null;
let retryCount = 0;
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
// 带超时的调用
const result = await CloudFunctionService.callWithTimeout(
functionName, params, config.timeout
);
const response = JSON.parse(result.result as string);
if (response.code === 0) {
return {
success: true,
data: response.data as T,
errorType: null,
errorMessage: '',
retryCount: attempt
};
} else {
// 业务错误,不重试
return {
success: false,
data: null,
errorType: FunctionErrorType.BUSINESS_ERROR,
errorMessage: response.message || '业务错误',
retryCount: attempt
};
}
} catch (error) {
lastError = error as Error;
retryCount = attempt;
// 判断错误类型
const errorType = CloudFunctionService.classifyError(error);
console.warn(`[CloudFunc] 第${attempt + 1}次调用失败: ${errorType}, ${lastError.message}`);
// 不可重试的错误直接返回
if (errorType === FunctionErrorType.BUSINESS_ERROR) {
return {
success: false,
data: null,
errorType: errorType,
errorMessage: lastError.message,
retryCount: attempt
};
}
// 限流错误,等待更长时间
if (errorType === FunctionErrorType.RATE_LIMIT_ERROR) {
await CloudFunctionService.sleep(5000);
} else if (attempt < config.maxRetries) {
// 其他可重试错误,指数退避
await CloudFunctionService.sleep(config.retryDelay * Math.pow(2, attempt));
}
}
}
return {
success: false,
data: null,
errorType: CloudFunctionService.classifyError(lastError),
errorMessage: lastError?.message || '未知错误',
retryCount: retryCount
};
}
/**
* 带超时的函数调用
*/
private static callWithTimeout(
functionName: string,
params: Record<string, Object>,
timeoutMs: number
): Promise<cloudFunctions.FunctionResult> {
return Promise.race([
cloudFunctions.call({ name: functionName, data: params }),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('调用超时')), timeoutMs)
)
]);
}
/**
* 错误分类
*/
private static classifyError(error: any): FunctionErrorType {
const message = error?.message || '';
const code = error?.code || 0;
if (message.includes('timeout') || message.includes('超时')) {
return FunctionErrorType.TIMEOUT_ERROR;
}
if (message.includes('network') || message.includes('网络')) {
return FunctionErrorType.NETWORK_ERROR;
}
if (code === 429 || message.includes('rate') || message.includes('限流')) {
return FunctionErrorType.RATE_LIMIT_ERROR;
}
if (code >= 500) {
return FunctionErrorType.SERVER_ERROR;
}
return FunctionErrorType.BUSINESS_ERROR;
}
// 缓存操作
private static getFromCache<T>(key: string): T | null {
const cached = CloudFunctionService.cache.get(key);
if (cached && cached.expireAt > Date.now()) {
return cached.data as T;
}
CloudFunctionService.cache.delete(key);
return null;
}
private static setToCache(key: string, data: Object, ttlMs: number): void {
CloudFunctionService.cache.set(key, {
data: data,
expireAt: Date.now() + ttlMs
});
}
private static sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
interface ArticleDetail {
id: string;
title: string;
content: string;
author: string;
}
async function loadArticle(articleId: string): Promise<ArticleDetail | null> {
const result = await CloudFunctionService.call<ArticleDetail>(
'getArticleDetail',
{ id: articleId },
{ timeout: 8000, maxRetries: 2, cacheTtl: 60000 } // 缓存1分钟
);
if (result.success) {
return result.data;
} else {
console.error(`获取文章失败: ${result.errorType} - ${result.errorMessage}`);
return null;
}
}
踩坑与注意事项
坑1:冷启动延迟
第一个请求或者长时间没请求后的第一次调用,延迟会明显偏高。解决方案:
- 减小函数体积:依赖越少,冷启动越快。别把整个
node_modules都打进去。 - 使用最小运行时:选择Node.js 18而不是Node.js 20,启动更快。
- 预热策略:对延迟敏感的函数,可以用定时触发器每5分钟调用一次,保持实例热状态。
坑2:函数执行时间限制
Cloud Functions有执行时间限制,HTTP触发器最长60秒,定时触发器最长900秒。如果你的逻辑跑不完,不是加时间的问题,是架构设计的问题。
解决方案:把耗时任务拆成多个步骤,用Cloud DB或消息队列串联。第一步写个状态,第二步根据状态继续执行。
坑3:环境变量不要存敏感信息
云函数的环境变量在控制台是明文展示的,谁都能看到。数据库密码、API密钥这类敏感信息,用AGC的密钥管理服务存储,函数运行时动态获取。
坑4:触发器的事件顺序不保证
Cloud DB触发器中,如果短时间内同一条数据被修改多次,触发器可能不会按修改顺序执行。你的函数逻辑必须幂等——同一个事件触发多次,结果一样。
坑5:并发限制
每个函数默认的并发实例数有上限(通常是1000)。如果你的应用流量突增,可能会触发限流。在AGC控制台可以申请提高配额,但得提前申请,不能等被限流了再搞。
坑6:日志查看的延迟
云函数的日志不是实时的,通常有1-3分钟的延迟。调试时别傻等日志刷新,先在本地用模拟器跑通,再部署到云端验证。
HarmonyOS 6适配说明
HarmonyOS 6对Cloud Functions Kit做了以下更新:
-
函数运行时升级:新增Node.js 20运行时支持,性能提升约15%。同时新增Python 3.11运行时,不限于JavaScript了。
-
流式响应:HTTP触发器新增Server-Sent Events(SSE)支持,函数可以流式返回数据,适合AI对话、实时日志等场景。
// HarmonyOS 6流式响应示例
exports.handler = async (event, context) => {
const response = new cloud.StreamingResponse();
response.setHeader('Content-Type', 'text/event-stream');
response.setHeader('Cache-Control', 'no-cache');
// 流式发送数据
for (let i = 0; i < 10; i++) {
response.write(`data: ${JSON.stringify({ chunk: i, text: `第${i}段内容` })}\n\n`);
await new Promise(resolve => setTimeout(resolve, 500));
}
response.end();
return response;
};
-
函数版本管理:新增函数版本和别名功能。你可以发布v1、v2版本,通过别名(如"stable"、“canary”)控制流量分配,实现灰度发布。
-
VPC访问:函数新增VPC配置,可以访问私有网络中的数据库和中间件。之前函数只能访问公网资源,现在可以直连内网服务了。
-
调试增强:新增本地调试工具
agconnect-cli debug,可以在本地模拟触发器事件,不用每次都部署到云端调试。
总结
Cloud Functions是"轻后端"的最佳选择。你不用管服务器,不用搞运维,写好函数直接部署。但Serverless不是银弹,冷启动、执行时间限制、并发上限这些坑你得心里有数。
核心记住三点:
- 冷启动是客观存在的,关键路径要做好预热或降级
- 函数必须幂等,触发器不保证事件顺序和唯一性
- 敏感信息别放环境变量,用密钥管理服务
| 评估维度 | 说明 |
|---|---|
| 学习难度 | ⭐⭐⭐ 函数编写简单,触发器配置和调试需要经验 |
| 使用频率 | ⭐⭐⭐⭐ 几乎所有应用都有需要服务端逻辑的场景 |
| 重要程度 | ⭐⭐⭐⭐ 没有云函数,敏感逻辑只能放客户端,安全隐患大 |
Cloud Functions让你从"全栈开发"变成"全栈但不碰运维",这才是现代应用开发的正确姿势。
- 点赞
- 收藏
- 关注作者
评论(0)