今天咱们来聊聊鸿蒙应用开发中一个非常实用的功能——端云一体化开发。如果你正在开发鸿蒙应用,可能会遇到这样的场景:应用需要一些后端服务,比如数据存储、用户认证、文件上传下载等等。传统做法是自己搭建服务器、配置环境、维护数据库,这些工作既费时又费力。

华为的Cloud Foundation Kit(云开发服务)就是为了解决这个问题而生的。它提供了云函数、云数据库、云存储、预加载等一整套云端服务,你只需要关注业务逻辑,服务器、操作系统、容器这些基础设施都由云端平台搞定。而且,在DevEco Studio中还提供了端云一体化开发的体验,前端开发人员可以轻松转变为全栈工程师,大大提高开发效率。

一、云开发服务能帮你做什么?

1.1 主要优势

低运维成本:不用自己构建和管理云端资源,Cloud Foundation Kit提供了函数计算、数据库、存储、预加载等一系列能力,你只需要调用相应的API就能使用。

弹性伸缩、按量计费:这个特点特别实用。比如你的应用在节假日访问量激增,云服务会自动扩容应对高峰;访问量下降后又自动缩容,释放闲置资源。你只需要为实际使用的资源付费,不用为空闲资源买单。

安全可靠:支持数据全密态加密,支持APP、用户和服务三重认证,还有基于角色的权限管理机制,全方位保障数据安全。

端云一体化开发:在DevEco Studio中可以同时开发端侧和云侧代码,调试、编译、部署都能在一个IDE里完成,非常方便。

在这里插入图片描述

1.2 典型应用场景

应用后端构建:快速构建应用的后端服务,简化开发和运维工作。

计算密集型任务:比如数据渲染、视频图像处理等任务,云函数可以自动分配足够的算力,任务完成后自动释放资源。还可以配置定时触发器,定时执行任务。

协议适配和转换:用于对接第三方平台,比如身份验证、消息队列、推送通知等功能。

浪涌式访问场景:像秒杀、节日活动这种访问量突然暴增的场景,云服务能快速自动扩容,保证系统稳定运行。

1.3 支持的设备和地区

云函数支持Phone、Tablet、Wearable、TV设备;云数据库、云存储支持Phone、Tablet、Wearable、TV设备;预加载支持Phone、Tablet设备。

支持的签名方式包括关联注册应用进行自动签名(DevEco Studio 6.0.0 Beta5及以上版本)和手动签名两种方式。从6.0.0(20) Beta5版本开始,支持模拟器开发。

二、准备工作

在开始开发之前,需要先完成以下的准备工作。

2.1 基本准备工作

按照"官网应用开发准备"完成基本准备工作,配置签名信息时,使用关联注册应用进行自动签名或者手动签名方式。

2.2 开通云服务

首次使用端云一体化开发前,需要开通云函数服务、云数据库服务、云存储服务、预加载服务。这些服务都是在AppGallery Connect 控制台中开通的。

三、云函数开发实战

云函数是Cloud Foundation Kit的核心功能之一,它允许你在云端运行代码,无需管理服务器。咱们通过一个实际案例来学习如何开发和使用云函数。

3.1 开发场景

假设你正在开发一个电商应用,需要一个功能:用户提交订单后,系统需要计算订单金额、生成订单号、发送确认邮件。这些操作如果放在端侧执行,可能会因为网络问题或者用户关闭应用而中断。如果放在云函数中执行,就能保证这些操作可靠地完成。

3.2 云函数代码结构

云函数使用Node.js开发,入口方法的定义如下:

module.exports.myHandler = function(event, context, callback, logger)

这里有几个关键参数:

  • myHandler:入口方法名称,你可以自定义
  • event:调用方传递的事件对象,JSON格式
  • context:函数运行时上下文对象,封装了日志接口、回调接口、环境变量等
  • callback:事件处理结果,必须显式调用callback(object)将结果返回给AGC
  • logger:记录日志,支持debug、error、warn、info四个级别

3.3 完整示例代码

下面是一个完整的云函数示例,展示了如何使用日志、环境变量、异常处理等功能:

let myHandler = function (event, context, callback, logger) {
  // 获取环境变量
  let env1 = context.env.env1;

  // 记录不同级别的日志
  logger.info("Test info log");
  logger.warn("Test warn log");
  logger.debug("Test debug log");
  logger.error("Test error log");

  logger.info("--------Start-------");
  try {
    let startTime = new Date().getTime();
    let endTime = startTime;
    let interval = 0;
    startTime = process.uptime() * 1000;

    // 打印输入参数和环境变量
    logger.info("request: " + JSON.stringify(event.request));
    logger.info("env1: " + env1);

    endTime = process.uptime() * 1000;
    interval = endTime - startTime;
    logger.info("intervalTime: " + interval);
    logger.info("--------Finished-------");

    let res = new context.HTTPResponse(context.env, {
      "res-type": "context.env",
      "faas-content-type": "json"
    }, "application/json", "200");
    res.body = { "intervalTime": interval };
    callback(res);
  } catch (error) {
    logger.error("--------Error-------");
    logger.error("error: " + error);
    callback(error);
  }
};

module.exports.myHandler = myHandler;

3.4 日志记录

在代码中使用logger接口记录日志,支持四种级别:

  • logger.debug():调试信息
  • logger.error():错误信息
  • logger.warn():警告信息
  • logger.info():普通信息

3.5 获取环境变量

环境变量可以在函数运行时读取和使用,访问方式如下:

let env1 = context.env.env1;

如果环境变量未配置,则会返回undefined。

3.6 异常处理

在函数代码中捕获异常,封装成error对象返回给调用方:

try {
  logger.info(JSON.stringify(event));
  let result = { message: "success" };
  callback(result);
} catch (err) {
  let error = {
    code: 400,
    message: err.message
  };
  callback(error);
}

error对象包含code(错误码)和message(错误描述)两个字段。

3.7 函数部署包结构

上传的nodejs函数部署包须使用如下结构:

my-function.zip
  |---- handler.js
  |---- node_modules
    |----async
    |----async-listener

处理程序所在代码文件(如handler.js)必须在zip包根目录下,依赖项放到node_modules目录下。可以通过npm工具安装依赖,例如npm install xxx命令。

四、在AGC中创建云函数

开通云函数服务后,需要在AGC中创建函数并添加代码。

4.1 创建步骤

  1. 登录AppGallery Connect,点击"开发与服务"
  2. 在项目列表中点击需要创建云函数的项目
  3. 在左侧导航栏选择"云开发 > 云函数",进入云函数主界面
  4. 选择"函数"页签,点击"创建函数"

在这里插入图片描述

4.2 函数配置

页面右侧抽屉式滑出"创建函数"窗口,按照"函数配置 -> 触发器 -> 函数代码 -> 层配置"引导顺序配置函数。

在这里插入图片描述

在"函数配置"页面,配置以下信息:

  • 函数名称:函数的名称
  • 描述:函数的描述信息
  • 触发方式:配置为"事件调用",表示通过触发器方式调用函数
  • 超时时长:函数最大运行时长,单位为秒,取值范围为1~1800
    • 同步调用方式时,最大运行时长为55秒
    • 异步调用方式时,最大运行时长为1800秒
  • 实例并发:函数请求并发量上限,单位为个,取值范围为1~10000
  • 环境变量:key-value形式,可以将需要的变量配置信息传入函数执行环境中

环境变量支持表单格式和JSON格式两种编辑方式。

表单格式编辑

在这里插入图片描述

JSON格式编辑
在这里插入图片描述

添加完成后,还可以点击"JSON格式导出",导出环境变量文件以备后续使用。

注意事项:

  • 环境变量的key值具有唯一性
  • "PROJECT_CREDENTIAL"和"AGC_"为系统级环境变量标识,不允许添加以其命名或以其为前缀的环境变量
  • 环境变量总数不超过1000个

4.3 触发器配置

进入"触发器"页面,配置HTTP触发器:

在这里插入图片描述

  • 触发器类型:HTTP触发器
  • 请求方式:目前仅支持POST请求方式
  • 认证类型
    • API客户端鉴权(Client适用):端侧网关认证,适用于来自APP客户端侧的函数调用
    • API客户端鉴权(Server适用):云侧网关认证,适用于来自APP服务器侧的函数调用
  • 启用decode:对于contentType为"application/x-www-form-urlencoded"的触发请求,是否使用URLDecoder对请求body进行解码

4.4 函数代码配置

进入"函数代码"页面,配置以下信息:

  • 运行环境:选择nodejs 20.x/latest,其中latest表示使用最新版本
  • 内存配置:函数容器所占有的内存大小,单位为MB,取值范围:500,1000,2000,4000
  • 代码输入类型:包括"在线编辑"与"*.zip文件"两种方式
  • 函数入口:包括入口文件名称和入口方法名称,通过"."连接。例如handler.myHandler
  • 代码文件:用于在线编辑函数代码或上传函数部署包
WebIDE功能

当"代码输入类型"选择"在线编辑"时,可以使用集成的WebIDE功能。WebIDE从左至右分两个部分:目录树、代码编辑器和最大化。

目录树功能

在这里插入图片描述

注意:如果在函数实例已经运行的情况下进行函数代码或配置更新,AGC后台会滚动更新函数实例,请耐心等待10-20秒。

4.5 层配置(可选)

层可以提供公共依赖库的发布与部署能力。开发者可以将函数依赖的公共库和相关依赖项提炼到层,通过为函数绑定层,便可以在函数中使用库,而不必将库包含在函数的代码包中。

在这里插入图片描述

一个函数最多可以绑定5个层。如果尚未创建层,可以跳过此步骤,后续创建层之后再进行绑定。

在这里插入图片描述

返回到“层配置”界面,绑定成功的层将展示在层列表中。如果需要解除层与函数的绑定关系,点击“解绑”即可。

在这里插入图片描述

五、测试云函数

函数创建后可以在AGC控制台测试函数的代码运行是否正常。

5.1 进入测试界面

有两种方式进入测试界面:

方式一:在函数列表中点击函数名称右侧"操作"列的"测试"

在这里插入图片描述

方式二:在函数列表中点击已创建的函数名称,进入函数详情页面,选择"函数代码"页签,点击"测试函数"

在这里插入图片描述

5.2 测试方式

使用默认测试事件

直接点击"测试"对函数进行测试。

在这里插入图片描述

创建新测试事件

如果需要设置调用函数的请求消息体,可以按照如下步骤配置测试参数:

  1. 在"事件"文本框中输入JSON格式的事件参数
  2. 点击"保存"
  3. 在弹出的提示框中输入事件名称
  4. 点击"确认"保存测试事件

在这里插入图片描述

在这里插入图片描述

"事件"文本框内输入的JSON对象,对应的是触发器的event事件格式,会透传给函数。

使用已保存测试事件

在"测试函数"界面,点击展开已保存的测试事件列表,选择已配置的事件名称右侧的"加载",然后点击"测试"。

5.3 查看测试结果

测试结果包含三部分:

  • 执行结果:展示测试后获得的响应结果
  • 运行日志:展示函数运行过程中,通过logger API打印的日志
  • 执行摘要:展示该次测试请求相关信息
    • 请求ID:该条测试请求的RequestID
    • 持续时间:函数执行的端到端时间
    • 执行版本:该次调用测试的具体函数版本

在这里插入图片描述

5.4 修改函数代码

"代码输入类型"为"在线编辑"的函数
可以直接在"函数代码"页签的代码编辑器中修改代码,然后点击页面底部的"提交"。更新成功后可以点击"测试函数"对更改后的代码进行测试。

在这里插入图片描述

“代码输入类型"为”.zip文件"的函数
需要在本地修改且打包完成后,点击重新上传函数部署包,然后点击页面底部的"提交"。更新成功后可以点击"测试函数"对更改后的代码进行测试。

如果代码更新量比较大,需要调整函数内存配置,可点击"内存配置"下拉框进行调整,然后再上传函数部署包。

函数测试无误后,可在"函数代码"页签点击"导出函数"导出函数部署包。导出包以"函数名称+函数版本.zip"格式命名。

在这里插入图片描述

六、在应用中调用云函数

6.1 设置网络权限

在"entry/src/main/module.json5"文件中添加网络权限:

"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  }
]

6.2 查询函数名和版本

在函数的触发器页面点击"HTTP触发器",查看"触发URL"的后缀,获取触发器的标识,格式为"函数名-版本号"。例如"myhandlerxxxx- l a t e s t " ,其中 " m y h a n d l e r x x x x " 为函数名, " latest",其中"myhandlerxxxx"为函数名," latest",其中"myhandlerxxxx"为函数名,"latest"为版本号。
在这里插入图片描述

6.3 调用云函数

在项目中导入cloudFunction组件:

import { cloudFunction } from '@kit.CloudFoundationKit';
import { BusinessError } from '@kit.BasicServicesKit';

调用call()方法设置函数,在方法中传入函数名称,返回调用结果。

使用Promise异步回调
import { hilog } from '@kit.PerformanceAnalysisKit';
import { cloudFunction } from '@kit.CloudFoundationKit';
import { BusinessError } from '@kit.BasicServicesKit';

function callFunction() {
  cloudFunction.call({
    name: 'functionName', // functionName需替换为实际的函数名
    version: '$latest',   // 如果不传入版本号,默认为"$latest"
    timeout: 10 * 1000,   // 单位为毫秒,默认为70*1000毫秒
    data: {               // data为函数请求体
      param1: 'val1',
      param2: 'val2'
    }
  }).then((value: cloudFunction.FunctionResult) => {
    hilog.info(0x0000, 'testTag', `Succeeded in calling the function, result: ${JSON.stringify(value.result)}`);
  }).catch((err: BusinessError) => {
    hilog.error(0x0000, 'testTag', `Failed to call the function, code: ${err.code}, message: ${err.message}`);
  })
}
使用callback异步回调
import { hilog } from '@kit.PerformanceAnalysisKit';
import { cloudFunction } from '@kit.CloudFoundationKit';
import { BusinessError } from '@kit.BasicServicesKit';

function callFunction() {
  cloudFunction.call({
    name: 'functionName', // functionName需替换成实际的函数名
    version: '$latest',  // 如果不传入版本号,默认为"$latest"
    timeout: 10 * 1000,  // 单位为毫秒,默认为70*1000毫秒
    data: {              // data为函数请求体
      param1: 'val1',
      param2: 'val2'
    }
  }, (err: BusinessError, value: cloudFunction.FunctionResult) => {
    if (err) {
      hilog.error(0x0000, 'testTag', `Failed to call the function, code: ${err.code}, message: ${err.message}`);
      return;
    }
    hilog.info(0x0000, 'testTag', `Succeeded in calling the function, result: ${JSON.stringify(value.result)}`);
  })
}

6.4 获取返回值

如果需要关注函数的返回值,可调用result属性获取:

let returnValue = value.result;

value为调用call()方法返回的cloudFunction.FunctionResult对象,返回值为云函数body返回的值。

七、本地调试云函数

从6.0.1(21)版本开始,新增支持调用本地云函数功能。这个功能对于开发调试非常有用,可以不用每次修改代码都上传到云端。

7.1 启动本地云函数

  1. 创建端云一体化开发工程:选择合适的云开发模板,根据工程向导创建端云一体化开发工程
  2. 开发云函数:使用DevEco Studio在端云一体化云侧工程下创建函数、开发函数、调试函数(通过本地调用方式调试函数)
  3. 调试函数过程中,如果下方通知栏的"cloudfunctions"窗口显示"Cloud Functions loaded successfully",则表示本地云函数启动成功,将生成本地函数的Function URI。请记录下该Function URI的域名和端口信息,例如"http://localhost:18090"

重要提示:由于本地云函数和部署至云端的函数获取请求体的方式不同,开发函数时必须按照如下示例获取请求体:

let body = event.body ? JSON.parse(event.body) : event;

7.2 设置端口映射

调用本地云函数之前,需要设置"设备端口"到"主机端口"的映射:

hdc rport tcp:18090 tcp:18090

其中,设备端口和主机端口即为Function URI中的端口。

7.3 调用本地云函数

使用Promise异步回调
function callFunctionLocal() {
  cloudFunction.call({
    name: 'my-cloud-function', // my-cloud-function需替换为实际的函数名
    version: '$latest',   // 如果不传入版本号,默认为"$latest"
    timeout: 10 * 1000,   // 单位为毫秒,默认为70*1000毫秒
    data: {               // data为函数请求体
      param1: 'val1',
      param2: 'val2'
    },
    localUrl: 'http://localhost:18090' // 本地启动的云函数地址
  }).then((value: cloudFunction.FunctionResult) => {
    hilog.info(0x0000, 'testTag', `Succeeded in calling the function, result: ${JSON.stringify(value.result)}`);
  }).catch((err: BusinessError) => {
    hilog.error(0x0000, 'testTag', `Failed to call the function, code: ${err.code}, message: ${err.message}`);
  })
}
使用callback异步回调
function callFunctionLocal() {
  cloudFunction.call({
    name: 'my-cloud-function', // my-cloud-function需替换为实际的函数名
    version: '$latest',   // 如果不传入版本号,默认为"$latest"
    timeout: 10 * 1000,   // 单位为毫秒,默认为70*1000毫秒
    data: {               // data为函数请求体
      param1: 'val1',
      param2: 'val2'
    },
    localUrl: 'http://localhost:18090' // 本地启动的云函数地址
  }, (err: BusinessError, value: cloudFunction.FunctionResult) => {
    if (err) {
      hilog.error(0x0000, 'testTag', `Failed to call the function, code: ${err.code}, message: ${err.message}`);
      return;
    }
    hilog.info(0x0000, 'testTag', `Succeeded in calling the function, result: ${JSON.stringify(value.result)}`);
  })
}

八、实际开发案例

8.1 场景描述

假设你正在开发一个社交应用,用户发布动态后,系统需要完成以下操作:

  1. 保存动态内容到数据库
  2. 提取动态中的关键词
  3. 根据关键词推荐相关用户
  4. 发送通知给相关用户

这些操作如果放在端侧执行,会占用用户设备的资源,而且可能因为网络问题导致操作失败。如果放在云函数中执行,就能保证这些操作可靠地完成,而且不占用用户设备资源。

8.2 云函数实现

let myHandler = function (event, context, callback, logger) {
  logger.info("--------Start processing post-------");
  try {
    let body = event.body ? JSON.parse(event.body) : event;
    let userId = body.userId;
    let content = body.content;

    logger.info("Processing post for user: " + userId);
    logger.info("Post content: " + content);

    // 1. 保存动态内容到数据库(这里只是示例,实际需要调用云数据库API)
    let postId = savePostToDatabase(userId, content);
    logger.info("Post saved with ID: " + postId);

    // 2. 提取动态中的关键词
    let keywords = extractKeywords(content);
    logger.info("Extracted keywords: " + JSON.stringify(keywords));

    // 3. 根据关键词推荐相关用户
    let recommendedUsers = recommendUsers(keywords);
    logger.info("Recommended users: " + JSON.stringify(recommendedUsers));

    // 4. 发送通知给相关用户(这里只是示例,实际需要调用推送服务API)
    sendNotifications(recommendedUsers, postId);
    logger.info("Notifications sent");

    let res = new context.HTTPResponse(context.env, {
      "res-type": "context.env",
      "faas-content-type": "json"
    }, "application/json", "200");
    res.body = {
      postId: postId,
      keywords: keywords,
      recommendedUsers: recommendedUsers
    };
    callback(res);
  } catch (error) {
    logger.error("--------Error processing post-------");
    logger.error("error: " + error);
    let errorObj = {
      code: 500,
      message: error.message
    };
    callback(errorObj);
  }
};

function savePostToDatabase(userId, content) {
  // 这里实现保存到数据库的逻辑
  // 返回动态ID
  return "post_" + Date.now();
}

function extractKeywords(content) {
  // 这里实现关键词提取的逻辑
  // 返回关键词数组
  return ["keyword1", "keyword2"];
}

function recommendUsers(keywords) {
  // 这里实现用户推荐的逻辑
  // 返回推荐用户列表
  return ["user1", "user2", "user3"];
}

function sendNotifications(users, postId) {
  // 这里实现发送通知的逻辑
  logger.info("Sending notifications to users: " + JSON.stringify(users));
}

module.exports.myHandler = myHandler;

8.3 端侧调用

import { hilog } from '@kit.PerformanceAnalysisKit';
import { cloudFunction } from '@kit.CloudFoundationKit';
import { BusinessError } from '@kit.BasicServicesKit';

function publishPost() {
  cloudFunction.call({
    name: 'process-post',
    version: '$latest',
    timeout: 30 * 1000,
    data: {
      userId: 'user123',
      content: '今天天气真好,适合出去游玩!'
    }
  }).then((value: cloudFunction.FunctionResult) => {
    hilog.info(0x0000, 'testTag', `Post published successfully, result: ${JSON.stringify(value.result)}`);
  }).catch((err: BusinessError) => {
    hilog.error(0x0000, 'testTag', `Failed to publish post, code: ${err.code}, message: ${err.message}`);
  })
}

九、注意事项

  1. 超时时长设置:根据实际业务需求设置合适的超时时长。同步调用最大55秒,异步调用最大1800秒。

  2. 实例并发配置:根据预期的并发量设置实例并发数,取值范围为1~10000。

  3. 内存配置:根据函数的内存需求选择合适的内存大小,取值范围:500,1000,2000,4000 MB。

  4. 环境变量管理:环境变量的key值具有唯一性,不要使用"PROJECT_CREDENTIAL"和"AGC_"作为前缀。

  5. 异常处理:在函数代码中一定要做好异常处理,避免因为未捕获的异常导致函数执行失败。

  6. 日志记录:合理使用不同级别的日志记录,方便调试和问题排查。

  7. 本地调试:开发时可以使用本地调试功能,提高开发效率。注意本地云函数和云端函数获取请求体的方式不同。

  8. 函数更新:如果在函数实例已经运行的情况下进行函数代码或配置更新,AGC后台会滚动更新函数实例,请耐心等待10-20秒。

十、总结

云函数是端云一体化开发的核心功能,它让你可以轻松地在云端运行代码,无需管理服务器。在实际开发中,你可以根据业务需求,灵活运用云函数来完成各种任务。

愣着干啥呢?还不麻溜的去上手试试!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐