Skip to content
🌊海洋蓝
🌸樱花粉
🍃森林绿
🔮幻夜紫
🌙暗夜黑

HarmonyOS Next 端云一体化完整教程

目录

  1. 端云一体化基础
  2. 云数据库操作
  3. 客户端操作云数据库
  4. 数据库查询条件
  5. 云存储操作
  6. 云函数开发

端云一体化基础

介绍

Cloud Foundation Kit(云开发服务)可以按需为应用提供云函数、云数据库、云存储等云端服务。应用运行所需的服务器和环境可以皆由云端平台提供,开发者只需关注应用的业务逻辑,而无需关心基础设施(例如:服务器、操作系统、容器等)。

DevEco Studio 中还提供了端云一体化开发的开发体验,您可以基于统一的技术栈,高效、协同地完成端、云代码的编写、调试、编译和部署,极大提高构建 HarmonyOS 应用和元服务的效率。

应用场景

  • 应用后端 快速构建应用或者元服务的后端服务,从而大幅简化应用开发与运维相关的事务,快速完成应用的构建
  • 计算密集型任务 当应用中出现计算密集型任务时,可以在云端及时申请足够的算力来支撑任务的执行。当任务结束时,可以立即释放资源,避免浪费。
  • 适配类应用 通过 Cloud Foundation Kit 实现协议类型的转换,比如实现 IoT 中不同设备的协议适配。以及接入第三方平台,通过第三方平台提供的接口,实现业务的接入或者协作。
  • 突发大量访问 传统架构服务在某些特殊场景下,可能出现大量的访问。为保证业务高峰时,系统能稳定运行,一般需要购买高性能、昂贵的服务器,组建集群负载均衡。但是,当业务回落时,就导致了大量服务器的资源浪费。

包含的资源

端云一体主要包含的资源有

  • 云函数
  • 云数据库
  • 云存储

适配的项目类型

端云一体可以用在开发鸿蒙的应用或者是元服务上。因为考虑应用的上架需要软著+备案,而元服务的上架只需要备案。所以后续的教程中为了更加方便学习,我们会在元服务的环境下进行。

学习流程

稍后我们会延续着以下步骤,让大家可以尽可能的掌握端云一体的开发流程。

image-20250116090935066

  1. AGC 平台新建元服务
  2. DevEco Studio 新建对应的项目工程
  3. 云函数的基本使用
  4. 云数据库的基本使用
  5. 云存储的基本使用

AGC 平台新建元服务

在 AGC 平台上新建服务的具体步骤可以参考这个文章 HarmonyOS Next 最新 元服务新建到上架全流程,这里就不再叙述了。

DevEco Studio 新建对应的项目工程

本地在使用 DevEco Studio 新建工程时,需要新建的是端云一体化的元服务项目。

image-20250116091157505

此时,你的项目中会看到额外的云端相关的目录结构。

image-20250116091237635

特别需要注意的是,目前模拟器是不支持运行端云一体的项目的。所以为了方便测试,最好是具体真机

运行到真机

image-20250116095501501

  • 测试下云函数

image-20250116095523881

  • 测试下云数据库

    还没有在云端配置云数据库,所以此时的测试是没有反应的。

    image-20250116095646627

  • 测试下云存储

    image-20250116095730657

云端环境一览

我们需要知道的是,在本地操作云端的资源时,本质上都是对云端的资源的一些操作。最终的操作成功与否,都会反映到真正的云端环境。

我们登录 AGC 平台,然后找到对应的项目。这里是元服务的名称,不是项目的名称,你的元服务是归属于某个项目的

image-20250116100212297

云函数一览

云函数其实就是存放后端逻辑的部分。也是我们后期编写后端业务主要区域。

image-20250116100026310

云数据库一览

image-20250116100343797

这里的字段解释如下

字段说明
对象类型表示你的数据中用到的实体的类型,比如用户、购物车都是不同的实体类型,也可以理解为你的数据表格
存储区理解为数据库的名称,你可以在这里管理多个存储区-数据库
数据你存储的实际的数据
  • 对象类型

    image-20250116100642788

  • 存储区

    image-20250116100701709

  • 数据

    image-20250116100720676

云存储一览

云存储就是存放你物理文件的地方,比如图片、视频、音频等等。

image-20250116100749650

DevEco Studio 中的云端资源

小伙伴们应该还记得,我们在新建端云一体云服务的时候,本地工程中也是多了一个文件夹的。 CloudProgram

image-20250116100943842

它里面的主要目录结构如下。

  1. clouddb 存放数据库相关的文件
  2. cloudfunctions 存放云函数相关的文件
  3. cloud-config.json 存放云端环境相关的配置

后期我们开发的思路可以是以下两种方式:

  1. 全部都在 AGC 平台上操作,如编写云函数逻辑代码、搭建数据库、数据表
  2. 尽量都在本地 DevEco Studio 工具中编写云函数逻辑代码、调试代码、搭建数据库、数据集表

以上两种方式都可以。但是为了刚好的开发体验,我们是建议使用方式 2.


云数据库操作

介绍

云数据库是端云协同的数据库产品,具备端云数据协同管理、统一数据模型及丰富数据管理 API 接口等能力。它采用基于对象模型 的数据存储结构,数据以对象(Object) 形式存于不同存储区,每个对象为一条完整数据记录。 对象类型(ObjectType) 定义存储对象集合,不同对象类型对应不同数据结构。存储区(Zone) 是独立的数据存储区域,每个存储区的对象类型定义完全相同。

学习流程

我们会按照以下流程来进行学习。

  1. 新建存储区:也就是新建数据库
  2. 创建对象类型:创建一个用于存储数据条目的对象类型。
  3. 添加数据条目:在刚刚创建的对象类型内添加一条条数据,并配置数据所在的存储区。
  4. 部署云数据库:数据成功添加后,您可以直接将该数据部署至 AGC 云端。您也可以等所有对象类型和数据条目开发完成后,再统一批量部署到 AGC 云端。

存储区

我们这里新建一个存储区 Study 用来存放稍后用到的数据。

image-20250116105318813

然后我们在 DevEco Studio 的 云端配置中指定存储区。

clouddb/db-config.json

{
	"defaultCloudDBZoneName": "Study", // 存储区的名称
	"defaultDataStorageLocation": "CN"
}

创建对象类型

什么是创建对象类型

创建对象类型可以理解为就是为我们的数据定义类型。类似于使用接口或者 class 来定义数据,但是这里操作的范围要更大。

  1. 定义基本的数据类型,如数字、字符串、布尔、日期时间等。
  2. 定义字段的权限,因为这个对象类型本身是数据表,定义权限相当于设置了哪一类用户拥有哪些权限。如读取、编辑、新增、删除等
  3. 定义索引,考虑数据量大需要比较方便的查找到要操作的数据,需要定义索引。

比如,我们需要根据书籍数据来定义类型。 Book

字段类型说明
idnumberid
namestring书名
pricenumber价格
publishdate出版日期
hotboolean是否热门
coverstring封面

对象类型的规范

新建 Book 对象类型文件

image-20250116130255215

这个对文件需要按照以下格式来编写。其中的字段的一些规范需要特别注意。

json
{
  "objectTypeName": 对象实体名称
  "fields": 对象中的属性
  "indexes": 索引
  "permissions" 权限
}
objectTypeName

表示实体对象的名称,我们这里可以是 Book

json
{
  "objectTypeName": "Book"
}
fields

fields 表示这个对象中属性的一些规则。

参数必选(M)/可选(O)说明
fieldNameM字段名称。输入要求具体如下:字段的名称长度必须大于或等于 1 个字符,小于或等于 30 个字符,只能包含以下 3 种类型,并且至少包含"字母"类型:字母(A-Z 或 a-z)数字(0-9)特殊字符:_字段名称必须以字母开头,以字母或者数字结尾。字段名称中不区分字母的大小写。修改对象类型时,支持删除字段。字段名称不允许使用系统保留字段名称: naturalbase_version、naturalbase_deleted、naturalbase_operationtype、naturalbase_creator、naturalbase_accesstime、naturalbase_operationtime、naturalbase_syncstatus、naturalbase_changedfieldsbitmap、naturalbase_lastmodifier、cmin、cmax、xmin、xmax、ctid、oid、tableoid、xc_node_id、tablebucketid、rowid。说明当前 Cloud Foundation Kit 暂不支持自增类型字段 IntAutoIncrement 或 LongAutoIncrement。
fieldTypeM字段的数据类型。当前支持的数据类型:String、Boolean、Byte、Short、Integer、Long、Float、Double、ByteArray、Text、Date、IntAutoIncrement(数字-自增)
belongPrimaryKeyO设置该字段是否为对象类型的主键,默认值为 false。至少设置一个字段为主键。支持设置复合主键,由多个字段组合成为主键,一个复合主键包含的字段小于等于 5 个,复合主键字段顺序与字段的顺序一致。数据类型为 ByteArray、Text、Date、Double、Float 和 Boolean 的字段不支持设置为主键。主键的值不允许更改。
notNullO设置字段值是否为非空,默认值为 false。数据类型为 ByteArray 和 Date 的字段不支持设置为非空。主键默认非空,且不允许更改。设置为非空的字段不支持加密和敏感。
isNeedEncryptO设置字段是否需要加密,开启全程加密数据管理功能,默认值为 false。选择加密后,该字段对应的数据会加密存储在存储区中。主键字段不支持加密。加密的字段不支持设置为非空。加密的字段不支持设置为敏感字段。一个对象类型中包含的加密字段和敏感字段的总数需小于或等于 5 个。字段设置为加密后,不支持导出该字段的数据值。数据类型为 ByteArray、Text 的字段不支持加密。对象类型创建成功后,不支持修改加密属性。
isSensitiveO设置字段是否为敏感字段,默认值为 false。选择敏感后,该字段对应的数据会加密存储在存储区中。敏感字段不支持设置为主键。敏感字段不支持设置为非空。敏感字段不支持设置为加密。敏感字段不支持设置为默认值。对象类型创建成功后,不支持修改敏感属性。仅支持数据类型为 Byte、Short、Integer、Long、Float、Double、String 和 Date 的字段设置为敏感字段。敏感字段不支持设置为索引。一个对象类型中包含的加密字段和敏感字段的总数需小于或等于 5 个。
defaultValueO字段为非空时,必须设置默认值。主键不支持设置默认值。加密字段和敏感字段不支持设置默认值。数据类型为 ByteArray、Date 不支持为其设置默认值。数据类型为 Text 的字段设置默认值时,默认值的长度小于或等于 200 个字符。

按照我们想要的书籍的规定。可以这样

fieldNamefieldTypebelongPrimaryKey
idIntAutoIncrementtrue
nameString-
priceDouble-
publishDate-
hotBoolean-
coverString-
json
{
  "objectTypeName": "Book",
  "fields": [
    {
      "fieldName": "id",
      "fieldType": "IntAutoIncrement",
      "belongPrimaryKey": true,
      "notNull": true
    },
    {
      "fieldName": "name",
      "fieldType": "String"
    },
    {
      "fieldName": "price",
      "fieldType": "Double"
    },
    {
      "fieldName": "publish",
      "fieldType": "Date"
    },
    {
      "fieldName": "hot",
      "fieldType": "Boolean"
    },
    {
      "fieldName": "cover",
      "fieldType": "String"
    }
  ]
}
indexes

indexes中为该对象类型配置索引、索引包含的字段、以及索引包含的字段的排序方式。

参数必选(M)/可选(O)说明
indexNameM索引名称。输入要求具体如下:索引的名称长度必须大于或等于 1 个字符,小于或等于 30 个字符,只能包含以下 3 种类型,并且至少包含"字母"类型:字母(A-Z 或 a-z)数字(0-9)特殊字符:_索引名称必须以字母开头。索引名称中不区分字母的大小写。修改对象类型时,仅支持新增或者删除索引。当删除索引后,本次提交前不允许新增同名索引。每个对象类型可以设置小于或等于 16 个索引。数据类型为 ByteArray 和 Text 的字段不支持设置为索引。
indexList > fieldNameM索引包含的字段。支持设置组合索引,由多个字段组合成为索引,一个组合索引包含的字段不超过 5 个。
indexList > sortTypeM索引包含的字段的排序方式,支持 ASC 升序或 DESC 降序。

这里我们为书籍的 id 和书籍的名称提供索引。

json
{
  "indexes": [
    {
      "indexName": "id_Index",
      "indexList": [
        {
          "fieldName": "id",
          "sortType": "ASC"
        }
      ]
    },
    {
      "indexName": "price_Index",
      "indexList": [
        {
          "fieldName": "price",
          "sortType": "DESC"
        }
      ]
    }
  ]
}
permissions

permissions 字段用来设置该数据表的操作权限的。

目前提供的角色和权限有以下分类。

参数必选(M)/可选(O)说明
roleM用户角色,包括:World:代表所有用户,包含认证和非认证用户。该角色默认拥有 Read 权限,可自定义配置 Upsert 和 Delete 权限。但是,不建议将 Upsert 和 Delete 权限配置给所有人角色。当对象类型中设置了加密字段之后,表示开启全程加密功能,此时所有人角色将不会拥有 Read、Upsert 和 Delete 权限,且不允许修改。Authenticated:经过 AGC 登录认证的用户。该角色默认拥有 Read 权限,可自定义配置 Upsert 和 Delete 权限。当对象类型中设置了加密字段之后,表示开启全程加密功能,此时认证用户角色将不会拥有 Read、Upsert 和 Delete 权限,且不允许修改。Creator:经过认证的数据创建用户。该角色默认拥有所有权限,且可自定义配置所有权限。每条数据都有其对应的数据创建人(即应用用户),每个数据创建者仅可以 Upsert 或者 Delete 自己创建的数据,不能 Upsert 或者 Delete 他人创建的数据。数据创建者的信息保存在数据记录的系统表中。Administrator:应用开发者,主要是指通过 AGC 控制台或 FaaS(Function as a Service,函数即服务)侧访问云数据库的角色。该角色默认拥有所有权限,且可自定义配置所有权限。Administrator 可以管理并配置其他角色的权限。
rightsM授予角色的权限,包括 Read、Upsert(包含新增和修改)和 Delete 权限。

接下来,我们为 这些角色设置以下的权限。

角色ReadUpsertDelete
World
Authenticated
Creator
Administrator

代码如下:

json
{
  "permissions": [
    {
      "role": "World",
      "rights": ["Read"]
    },
    {
      "role": "Authenticated",
      "rights": ["Read", "Upsert"]
    },
    {
      "role": "Creator",
      "rights": ["Read", "Upsert", "Delete"]
    },
    {
      "role": "Administrator",
      "rights": ["Read", "Upsert", "Delete"]
    }
  ]
}

创建数据条目

我们搭建好了对象类型,现在可以根据它来插入数据了。

  1. 新建数据文件

    image-20250116160900005


    image-20250116160911750

  2. 插入数据 这里可以看到一些基本数据

    json
    {
      "cloudDBZoneName": "Study",
      "objectTypeName": "Book",
      "objects": [
        {
          "id": 1,
          "name": "string1",
          "price": 10.5,
          "publish": 1737014949576,
          "hot": true,
          "cover": "string1"
        },
        {
          "id": 2,
          "name": "string2",
          "price": 20.5,
          "publish": 1737014949576,
          "hot": false,
          "cover": "string2"
        }
      ]
    }

    字段解释

    • cloudDBZoneName:配置存储区名称。
    • objects:配置当前对象类型中所有字段的值,即写入数据。一个对象(object)即为一条数据,您可以通过新建一个对象(object)来为字段赋新值,也可以修改某个对象(object)下字段的值(主键或加密字段的值不支持修改)

部署云数据库

刚才我们都是在本地搭建的数据库,现在我们需要将这些数据同步到 AGC 平台的数据库上。

部署的方式也很简单

  1. 部署

image-20250116161334518

  1. 成本

image-20250116170856484

  1. 刷新 AGC 平台上的数据库

    image-20250116170956502


客户端操作云数据库

上一章我们主要讲解了如何新建数据库、新建数据表已经部署数据库。这一章主要学习如何对数据库、数据表进行 CRUD 的操作。

操作数据库的方式

我们操作数据库的方式一共有 4 种。

  1. 可视化 - AGC 平台上直接编辑数据
  2. 可视化 - DevEco Studio 中直接编辑数据
  3. 编程 - 客户端通过代码的方式操作数据
  4. 编程 - 云函数通过代码的方式操作数据

方式 1、2 都是为了让开发人员简单、方便管理数据。但是实际的业务场景中,我们更多要关注的是 3、4 的方式。那么本章主要讲的是 方式 3-客户端通过代码的方式操作数据 。后续再讲到云函数的时候再来补充方式 4。

生成客户端-数据模型

先解释下这个功能是做什么的。因为我们的目标是要在 客户端来查询数据库的数据,那必不可少需要在客户端中定义数据表实体的类型。然后 DevEco Studio 提供了比较便捷的根据数据实体生成客户端-数据模型。

image-20250117001110198

我这里红色的提示是因为我之前已经生成过了,所以提示是否覆盖。

成功后边得到如下内容:entry/src/main/ets/common/types/Book.ts

typescript
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 * Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
 */
import { cloudDatabase } from "@kit.CloudFoundationKit";

class Book extends cloudDatabase.DatabaseObject {
  id: number;
  name: string;
  price: number;
  publish: Date;
  hot: boolean;
  cover: string;

  naturalbase_ClassName(): string {
    return "Book";
  }
}

export { Book };

简单使用

接下来我们就可以进入客户端查询数据库的步骤了。

  1. 首先我们需要创建一个数据库示例,每一个存储区就是一个数据库 cloudDatabase.DatabaseZone
  2. 然后指定查询条件,比如全部查询、查询 id 等于 1 等等 condition
  3. 进行查询,接收返回的数据
jsx
import { cloudDatabase } from '@kit.CloudFoundationKit';
import { Book } from '../common/types/Book';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct PageDB {
  // 数据库实例,初始化时为 undefined
  agcDataBase: cloudDatabase.DatabaseZone | undefined = undefined;
  // 查询条件实例,初始化时为 undefined
  condition: cloudDatabase.DatabaseQuery<cloudDatabase.DatabaseObject> | undefined = undefined;
  // 初始化数据库连接的方法
  fn1 = () => {
    this.agcDataBase = cloudDatabase.zone('Study');
    promptAction.showToast({ message: `初始化成功` });
  }
  // 查询数据库的方法
  fn2 = async () => {
    try {
      // 创建查询条件实例
      this.condition = new cloudDatabase.DatabaseQuery(Book);
      // 设置查询结果的最大数量为 10
      this.condition.limit(10);
      // 执行查询并获取结果
      const resultArray = await this.agcDataBase?.query(this.condition);
      // 显示查询结果
      AlertDialog.show({ message: JSON.stringify(resultArray, null, 2) });
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` });
      console.error(e.message, e.code);
    }
  }

  build() {
    Column({ space: 10 }) {
      Button("初始化1")
        .onClick(this.fn1)

      Button("查询2")
        .onClick(this.fn2)
    }
    .height('100%')
    .width('100%')
  }
}

image-20250117001731385

对数据表的操作

端云一体提供了基本的对数据表的操作。主要分成以下几种

操作类型说明
query查询
upsert新增或者编辑
delete删除
calculateQuery计算

query

就是查询,上面的示例中已经使用过了。

upsert - 新增

现在主要演示 使用 upsert 实现新增

upsert 方法可以接收一个或者多个数据实体。如果该数据之前不存在,这时的 upsert 表示新增,反之表示更新。

操作成功后,会返回成功影响了的数据的数量。

比如新增:

jsx
fn3 = async () => {
  try {
    const book = new Book();
    book.id = parseInt(Date.now().toString().slice(0, 6)); // 正常应该是自增的,但是这个自增会出bug
    book.name = "book";
    book.price = 99;
    book.publish = new Date();
    book.hot = true;
    book.cover = "xxxx";
    const res = await this.agcDataBase?.upsert(book);
    AlertDialog.show({ message: `新增成了${res}条` });
  } catch (e) {
    promptAction.showToast({ message: `${e.message} ${e.code}` });
    console.error(e.message, e.code);
  }
};

Button("新增3").onClick(this.fn3);

image-20250117011316637

需要注意的是:我们当前的角色是 World,此时是没有 新增、编辑、删除权限的。所以为了方便操作,可以修改调整权限

clouddb/objecttype/Book.json

json
    {
      "role": "World",
      "rights": [
        "Read",
        "Upsert",
        "Delete"
      ]
    },

当数据表信息发生了修改时,需要在 AGC 平台上删除之前的数据区+数据表。然后重新部署。

upsert - 编辑

这里我们可以根据 id 来编辑一下数据。 数据库里面存放着id:10 的数据,我们就来修改它。

image-20250117201256407

jsx
fn4 = async () => {
  try {
    const book = new Book();
    //  固定修改id为10的数据
    book.id = 10;
    book.name = "更新book";
    book.price = 999;
    book.publish = new Date();
    book.hot = true;
    book.cover = "更新 xxxx";

    const res = await this.agcDataBase?.upsert(book); // 因为数据 id已经存在,所以此时是编辑
    AlertDialog.show({ message: `编辑成功${res}条` });
  } catch (e) {
    promptAction.showToast({ message: `${e.message} ${e.code}` });
    console.error(e.message, e.code);
  }
};

Button("更新4").onClick(this.fn4);

delete - 删除

执行删除 delete 方法时,也是需要传入一个或者多个删除的元素。

我们这里就可以根据 id:10 的元素执行删除。

jsx
fn5 = async () => {
  try {
    const book = new Book();
    //  固定修改id为10的数据
    book.id = 10;
    const res = await this.agcDataBase?.delete(book); // 因为数据 id已经存在,所以此时是编辑
    AlertDialog.show({ message: `删除成功${res}条` });
  } catch (e) {
    promptAction.showToast({ message: `${e.message} ${e.code}` });
    console.error(e.message, e.code);
  }
};

Button("删除5").onClick(this.fn5);

calculateQuery - 计算

calculateQuery 从数据库中查询符合条件的数据,并对指定字段进行算术计算。主要提供了以下的计算功能。

名称说明
AVERAGE0计算平均数。
SUM1计算总和。
MAXIMUM2计算最大值。
MINIMUM3计算最小值。
COUNT4计算记录总数。

image-20250117203951021

jsx
fn6 = async () => {
  try {
    // 创建查询条件实例
    this.condition = new cloudDatabase.DatabaseQuery(Book);
    // 设置查询结果的最大数量为 10
    this.condition.limit(10);
    // 执行查询并获取结果
    const resultArray = await this.agcDataBase?.calculateQuery(
      this.condition,
      "price",
      cloudDatabase.QueryCalculate.SUM
    );
    // 显示查询结果
    AlertDialog.show({ message: JSON.stringify(resultArray, null, 2) });
  } catch (e) {
    promptAction.showToast({ message: `${e.message} ${e.code}` });
    console.error(e.message, e.code);
  }
};

Button("计算6 总价格").onClick(this.fn6);

数据库查询条件

在上一章节我们讲了数据库数据表的一些基本操作。如query、upsert、delete和calculateQuery。这一章节主要来讲解各种查询条件操作。如 查询班级年龄大于30的同学等。

查询条件解释

谓词,用来代替或者展示其客体性质、特征或者客体之间关系的词项

这些查询条件在端云一体中解释中叫做谓词。云数据库中提供丰富的谓词查询来构建查询条件。根据谓词查询方法构造自己的DatabaseQuery对象。

查询条件谓词一览

关键字说明
equalTo表示等于的条件判断,用于查询中筛选出与指定值相等的数据
notEqualTo表示不等于的条件判断,筛选出与指定值不相等的数据
beginsWith表示以某个值开头,用于查询开头匹配特定字符串的数据
endsWith表示以某个值结尾,用于查询结尾匹配特定字符串的数据
contains表示包含某个值,用于查询包含特定字符串的数据
greaterThan表示大于,用于数值类型数据的比较,筛选出大于指定值的数据
greaterThanOrEqualTo表示大于或等于,筛选出大于或等于指定值的数据
lessThan表示小于,用于数值类型数据的比较,筛选出小于指定值的数据
lessThanOrEqualTo表示小于或等于,筛选出小于或等于指定值的数据
in用于判断某个值是否在指定的集合内,常用于查询符合多个值中某一个的数据
isNull用于判断某个字段是否为空值
isNotNull用于判断某个字段是否不为空值
orderByAsc按升序排列,用于对查询结果按照指定字段进行从小到大的排序
orderByDesc按降序排列,用于对查询结果按照指定字段进行从大到小的排序
limit限制查询结果返回的数量
beginGroup开始一个逻辑分组,用于将多个条件组合在一起作为一个逻辑单元
endGroup结束一个逻辑分组
or逻辑或,用于连接多个条件,只要其中一个条件满足则整个逻辑表达式为真
and逻辑与,用于连接多个条件,只有所有条件都满足时整个逻辑表达式才为真

谓词使用示例

equalTo 查询id为20的数据

typescript
this.condition.equalTo("id", 20)

notEqualTo 查询id不等于20的数据

typescript
this.condition.notEqualTo("id", 20)

beginsWith 查询name字段以b开头的数据

typescript
this.condition.beginsWith("name", "b")

endsWith 查询name字段以k结尾的数据

typescript
this.condition.endsWith("name", "k")

contains 查询name字段包含k的数据

typescript
this.condition.contains("name", "k")

greaterThan 查询price字段大于30的数据

typescript
this.condition.greaterThan("price", 30)

greaterThanOrEqualTo 查询price字段大于或者等于30的数据

typescript
this.condition.greaterThanOrEqualTo("price", 30)

lessThan 查询price字段小于30的数据

typescript
this.condition.lessThan("price", 30)

lessThanOrEqualTo 查询price字段小于或者等于30的数据

typescript
this.condition.lessThanOrEqualTo("price", 30)

in 查询name字段包含在["book","aaaa","bbbb"]的中数据

typescript
this.condition.in("name", ["book", "aaaa", "bbbb"])

isNull 查询name字段是否为null

typescript
this.condition.isNull("name")

isNotNull 查询name字段是否非null

typescript
this.condition.isNotNull("name")

orderByAsc 根据id,进行升序

typescript
this.condition.orderByAsc("id")

orderByDesc 根据id,进行降序

typescript
this.condition.orderByDesc("id")

limit 查询2条数据,从第1条开始

typescript
this.condition.limit(2, 1)

or 逻辑或,查询name=book 或者 price>30的数据

typescript
this.condition.equalTo("name", "book").or().greaterThan('price', 30)

and 逻辑与,查询name=book123 并且 price>30的数据

typescript
this.condition.equalTo("name", "book123").and().greaterThan('price', 30)

beginGroup 和 endGroup 表示一对逻辑分组

typescript
 // 条件1:   name=book并且price>30

  // 条件2:   id=20或者price>30

  // 需求: 查询 条件1 和 条件2 同时满足的数据
  this.condition
    .beginGroup()
    .equalTo('name', 30)
    .and()
    .greaterThan('price', 30)
    .endGroup()
    .and()
    .beginGroup()
    .equalTo('id', 20)
    .or()
    .greaterThan('price', 30)
    .endGroup();

加强

上面的谓词,也是根据实际语义搭配一起使用。比如:查询name=book的前2条数据


云存储操作

上一章节我们主要讲解了查询条件-谓词的基本使用技巧。这一章我们主要来讲解下客户端操作云存储。

云存储介绍

云储存就是提供了一个可以存储物理文件的云端环境,比如存储图片、视频、音乐等,同时提供了的客户端操作云存储、云函数操作云存储的能力。我们这里主要讲解客户端操作存储,后续会讲解云函数操作云存储。

云存储的计费策略

免费配额

开通云存储服务后,华为供了免费额度以供试用,具体的配额明细如下。

计费项详细说明免费配额
存储存储数据的容量,以小时为统计周期,UTC时间整点结算,单位为GB。5GB注意"存储"为按月计费,而非一次性计费。如果您使用的存储容量每月都超过免费配额,您每月都需支付相应的超额费用。例如,本月您使用了6GB存储容量,则本月您需支付1GB的超额费用。如果下个月您服务的存储容量为7GB,下个月您仍需支付2GB的超额费用。
网络出站流量公网流出流量,即通过互联网从云存储下载数据产生的流量。1GB/天
上传操作次数上传接口请求次数。20,000/天
下载操作次数下载接口请求次数。50,000/天
每个项目多个存储实例单个项目支持创建多个存储实例。免费档不支持此功能

以某工具类APP为例,月新增下载量近1w,云存储提供的免费配额完全能支撑APP日常的调用。

升级到按量付费档

当统计周期内的免费配额即将用尽时,您可以选择升级到按量付费档,以继续使用服务。或者,您也可以等到下个统计周期再使用云存储服务,在此之前服务将不再可用。

云存储按量付费价格如下表所示,套餐升级操作请参见升级到付费档

计费项详细说明按量付费价格
存储存储数据的容量,以小时为统计周期,UTC时间整点结算,单位为GB。CNY 0.1679/GB
网络出站流量公网流出流量,即通过互联网从云存储下载数据产生的流量。CNY 0.7751/GB
上传操作次数上传接口请求次数。CNY 0.323/10,000
下载操作次数下载接口请求次数。CNY 0.0258/10,000
每个项目多个存储实例单个项目支持创建多个存储实例。按量付费档支持此功能

云存储核心功能

客户端操作存储的核心功能主要有以下。

  • 上传文件到云端
  • 查看云端文件列表
  • 查看云端文件元数据
  • 设置云端文件数据
  • 获取云端文件下载地址
  • 下载云端文件到本地
  • 删除云端文件

接下来我们便开始对云存储进行操作。

准备环境

开通云存储

我们需要提前在AGC平台上开通云存储环境。

image-20250118233735515

我们可以看到,这个的云存储的实例名称为 default-bucket-xxxx

初始化云存储实例

因为后期要操作云存储都需要用到云存储实例。所以需要初始化好。

使用默认云存储实例
typescript
 bucket: cloudStorage.StorageBucket = cloudStorage.bucket(); // 获取默认的存储实例
指定云存储实例
typescript
 bucket: cloudStorage.StorageBucket = cloudStorage.bucket("default-bucket-xxxx"); // 获取默认的存储实例

上传文件到云端

上传文件到云端只能调用StorageBucket.uploadFile方法,但是该方法要求上传的文件路径必须存放在context.cacheDir目录下。因此需要先提前做好这个处理。

步骤:

  1. 选择待上传的文件,下方示例代码中使用photoAccessHelper.PhotoViewPicker指定需要上传的文件。
  2. 将待上传的文件复制到context.cacheDir目录下。
  3. 调用StorageBucket.uploadFile接口创建上传任务,监听上传任务的progress、completed、failed等事件。
  4. 启动上传任务。

uploadFile在上传文件时,还支持上传自定义的标准的http头部信息。具体可以查看API说明

示例代码:

jsx
fn8 = async () => {
    // 使用photoAccessHelper选择指定的文件
    let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    // 设置媒体文件类型为图像
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    // 设置选择媒体文件的最大数目为1
    photoSelectOptions.maxSelectNumber = 1;
    let photoViewPicker = new photoAccessHelper.PhotoViewPicker();
    // 调用select方法选择图片,并处理选择结果
    photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
      // 获取选中文件的URI
      let fileUri = photoSelectResult.photoUris[0];
      console.info(`pick file ${fileUri}`);
      // 提取文件名
      let fileName = fileUri.split('/').pop() as string;
      console.info(`file name ${fileName}`);
      // 创建缓存文件名,以当前时间戳和原文件名组合
      let cacheFile = `${Date.now()}_${fileName}`;
      console.info(`cacheFile ${cacheFile}`);
      // 拼接缓存文件路径
      let cacheFilePath = getContext().cacheDir + '/' + cacheFile;

      // 将选中文件复制到缓存目录下,文件名为cacheFile
      try {
        // 打开源文件
        let srcFile = fs.openSync(fileUri);
        // 打开目标文件(创建或读写)
        let dstFile = fs.openSync(cacheFilePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
        // 复制文件内容
        fs.copyFileSync(srcFile.fd, dstFile.fd);
        // 关闭源文件
        fs.closeSync(srcFile);
        // 关闭目标文件
        fs.closeSync(dstFile);
      } catch (e) {
        console.info(`copy file failed ${e.message}`);
        return;
      }

      // 使用默认实例上传文件至云存储
      this.bucket.uploadFile(getContext(this), {
        // 本地文件路径,位于context.cacheDir目录下
        localPath: cacheFile,
        // 云端存储路径
        cloudPath: fileName
      }).then((task: request.agent.Task) => {
        // 监听任务进度
        task.on('progress', (progress) => {
          console.info(`on progress ${JSON.stringify(progress)}`);
        });
        // 监听任务完成
        task.on('completed', (progress) => {
          console.info(`on completed ${JSON.stringify(progress)}`);
        });
        // 监听任务失败
        task.on('failed', (progress) => {
          console.error(`on failed ${JSON.stringify(progress)}`);
        });
        // 监听任务响应
        task.on('response', (response) => {
          console.info(`on response ${JSON.stringify(response)}`);
        });

        // 启动任务,并处理启动结果
        task.start((err: BusinessError) => {
          if (err) {
            console.error(`Failed to start the uploadFile task, Code: ${err.code}, message: ${err.message}`);
          } else {
            console.info(`Succeeded in starting a uploadFile task.`);
          }
        });
      }).catch((err: BusinessError) => {
        console.error(`uploadFile failed, Code: ${err.code}, message: ${err.message}`);
      });
    })
}

      Button("计算8 上传文件到云端")
        .onClick(this.fn8)

刷新AGC-中的云存储: 可以看到文件成功上传了。

image-20250118235504943

查看云端文件列表

如果想要获取云端文件列表,可以使用 StorageBucket.list API。

参数

参数名类型必填说明
cloudPathstring云侧文件路径。
optionsListOptions列举操作的相关参数。

ListOptions

名称类型只读可选说明
maxResultsnumber列举文件的最大数量,取值范围1-1000,默认则列举所有文件。
pageMarkerstring分页标识。

返回内容

名称类型只读可选说明
directoriesstring[]列举操作返回的云侧目录列表。
filesstring[]列举操作返回的云侧文件列表。
pageMarkerstring分页标识。

示例代码

typescript
  fn9 = async () => {
    try {
      const res = await this.bucket.list('') // 获取根据根路径
      // const res = await this.bucket.list('avatar/') // 获取 avatar/ 路径下的文件。 需要注意的是 如果你输入的路径是 ava ,那么 avatar也会被匹配到
      AlertDialog.show({ message: JSON.stringify(res, null, 2) })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }

    Button("计算9 获取云端文件列表")
    .onClick(this.fn9)

得到结果

image-20250119000030512

查看云端文件元数据

我们可以通过该StorageBucket.getMetadataAPI获取到文件名、文件大小、文件类型等常用属性,也包括用户自定义的文件属性。

参数

参数名类型必填说明
cloudPathstring云侧文件路径。

返回值

类型说明
Promise<Metadata>Promise对象,返回云侧文件的元数据信息。

示例代码

typescript
  fn10 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      const res2 = await this.bucket.getMetadata(cloudFilePath)
      AlertDialog.show({ message: JSON.stringify(res2, null, 2) })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }

Button("计算10 查看云端文件元数据")
.onClick(this.fn10)

得到结果

image-20250119001021913

获取云端文件下载地址

我们之前通过getMetadata获取到了云端文件的相关信息。但是如果该文件是图片,而我们想要使用Image显示该图片,那么还需要使用StorageBucket.getDownloadURL获取到该文件的下载地址。

参数

参数名类型必填说明
cloudPathstring云侧文件路径。

返回值

类型说明
Promise<string>Promise对象,返回云侧文件下载地址。

示例代码

jsx
  fn11 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      const res2 = await this.bucket.getDownloadURL(cloudFilePath)
      AlertDialog.show({ message: JSON.stringify(res2, null, 2) })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }

Button("计算11 获取云端文件下载地址")
.onClick(this.fn11)

得到结果

image-20250119021913620

下载云端文件到本地

利用 StorageBucket.downloadFile 可以将文件下载到 ontext.cacheDir 目录下。

参数

参数名类型必填说明
contextcommon.BaseContext应用上下文。
parametersDownloadParams下载相关参数。

DownloadParams

名称类型只读可选说明
localPathstring本地文件路径,根路径为cache目录。
cloudPathstring云侧文件路径。
moderequest.agent.Mode下载任务类型,前端任务在应用切换到后台一段时间后失败/暂停;后台任务不受影响。默认为BACKGROUND。BACKGROUND:后台任务。FOREGROUND:前端任务。
overwriteboolean当本地文件已存在时,是否覆盖本地文件,默认false。true:覆盖本地文件。false:不覆盖,若存在同名文件则下载失败。
networkrequest.agent.Network下载任务的网络配置,网络不满足设置条件时,未执行的任务等待执行,执行中的任务失败/暂停。默认为ANY。ANY:不限网络类型。WIFI:无线网络。CELLULAR:蜂窝数据网络。

返回值

类型说明
Promise<[Task]>Promise对象,返回下载任务。

示例代码

jsx
  fn12 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      const task = await this.bucket.downloadFile(getContext(this), {
        localPath: Date.now().toString(), // 本地文件路径, 下载成功后,文件将会保存在context.cacheDir目录
        cloudPath: cloudFilePath  // 云侧文件路径
      })
      task.on('progress', (progress) => {
        console.info(`on progress ${JSON.stringify(progress)} `);
      });
      task.on('completed', (progress) => {
        console.info(`on completed ${JSON.stringify(progress)} `);
        AlertDialog.show({ message: JSON.stringify("下载完成", null, 2) })
      });
      task.on('failed', (progress) => {
        console.error(`on failed ${JSON.stringify(progress)} `);
      });
      task.on('response', (response) => {
        console.info(`on response ${JSON.stringify(response)} `);
      });
      task.start((err: BusinessError) => {
        if (err) {
          console.error(`Failed to start the downloadFile task, Code: ${err.code}, message: ${err.message}`);
        } else {
          console.info(`Succeeded in starting a downloadFile task. result: ${task.tid}`);
        }
      });
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }


  Button("计算12 下载云端文件")
    .onClick(this.fn12)

得到结果

image-20250119022910282

删除云端文件

调用StorageBucket.deleteFile删除云侧的文件。

参数

参数名类型必填说明
cloudPathstring云侧文件路径。

返回值

类型说明
Promise<void>Promise对象。无返回结果的Promise对象。

示例代码

  fn13 = async () => {
    try {
      const res = await this.bucket.list('')
      const cloudFilePath = res.files[0] // 获取第一个文件 -  实际开发中,你需要明确该路径下一定存在文件
      await this.bucket.deleteFile(cloudFilePath)
      promptAction.showToast({ message: `${"成功"}` })
    } catch (e) {
      promptAction.showToast({ message: `${e.message} ${e.code}` })
    }
  }

得到结果

image-20250119023215094


云函数开发

之前的文章中把云数据库、云存储都讲过了,这一章节要讲解的是云函数

云函数介绍

云函数其实就是serverless技术。可以理解云函数就实现传统后端中的具体业务,而无需关心服务器购买、部署、安全、性能等一系列相关问题,专注于具体的业务开发。HarmonyOS Next的云函数采用的是typescript的语法,这对熟悉js或者熟悉ArkTs的同学来说都很容易上手。

另外云函数具有调用其他云函数、调用第三方接口、调用云存储、调用云数据库的能力。对于段云一体化开发的应用来说,可以根据这样的场景来使用云函数。

  1. 简单的数据库查询、云端文件的管理可以直接使用客户端操作的方式。
  2. 繁琐或者涉及安全的操作,可以把业务抽离到云函数端,这样更加容易管理项目。

本章节也会讲解如何云函数的开发、创建、调试、部署,以及在云函数端调用其他云函数、调用第三方接口、调用云存储、调用云数据库。

创建云函数

可以选择创建云函数或者云对象。这里建议选择云对象更加方便业务逻辑的实现。为什么呢,举个例子。书籍的crud刚好放在一个对象中,增、删、改、查都可以做个一个对象的属性存在,更加容易方便管理。比如以下示例。

  • 云函数一览

    typescript
    // 云函数
    let myHandler = async function (event, context, callback, logger) {
      //   这里写不同的业务逻辑,都是都耦合在一个myHandler中
      callback();
    };
    
    export { myHandler }
  • 云对象一览

    typescript
    export class Book {
      //   增加
      add() {
        return {};
      }
    
      //   删除
      delete() {
      }
    
      //   修改
      update() {
      }
    
      //   查询
      query() {
      }
    }

新建云对象,云对象名称为book。

image-20250208224404854

得到以下新文件,其中 book.ts 是编写云对象book具体业务逻辑的。function-config.json是关于该云对象的相关配置如是否鉴权等。package.json是该云对象的描述文件,具体作用类似 oh-package.json,后续安装第三方依赖也会在这里登记信息。

image-20250208225612809

function-config.json

文件内容如下。

json
{
  "handler": "book.Book",
  "functionType": 1,
  "triggers": [
    {
      "type": "http",
      "properties": {
        "enableUrlDecode": true,
        "authFlag": "true",
        "authAlgor": "HDA-SYSTEM",
        "authType": "apigw-client"
      }
    }
  ]
}

相关解析如下

  • handler 表示云对象的入口

  • functionType 表示函数类型,"0"表示云函数,"1"表示云对象。"functionType"的值为创建时自动生成,不可手动修改,否则将导致云函数部署失败。

  • triggers 表示触发云对象的函数

    • type:触发器类型,配置为"http"。

    • properties:触发器属性,属性参数如下表所示。

      参数说明
      enableUrlDecode通过HTTP触发器触发函数时,对于contentType为"application/x-www-form-urlencoded"的触发请求,是否使用URLDecoder对请求body进行解码再转发到函数中。true:启用。false:不启用。
      authFlag是否鉴权,默认为true。
      authAlgor鉴权算法,默认为HDA-SYSTEM。
      authTypeHTTP触发器的认证类型。apigw-client:端侧网关认证,适用于来自APP客户端侧(即本地应用或者项目)的函数调用。cloudgw-client:云侧网关认证,适用于来自APP服务器侧(即云函数)的函数调用。

oh-package.json

文件内容如下。

json
{
  "name": "book",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
  }
}

相关解释如下。

  • name:表示项目的名称
  • version:项目的版本号
  • description:项目的描述
  • scripts:一个包含各种脚本命令的对象
  • author:项目作者信息
  • license:项目所使用的开源许可证,这里是 "ISC"
  • dependencies:项目的依赖信息

book.ts

book.ts的文件解析如下。

  1. methods1表示云对象具体的方法,可以有多个。
  2. params1params2是传递给该云对象的参数
  3. return 的内容是返回给云对象的调用者

image-20250208224438567

调试云函数/对象

在开发云对象时,经常需要编写繁琐的业务逻辑,那么就必不可少对象的调试。DevEco Studio 内置了云对象的调试面板。按照以下步骤操作就可以方便对云对象进行调试了。

  1. 启动云对象调试,鼠标右键你要调试的云对象、选择 Debug 'book'

    image-20250208225401478

  2. 打开云对象调试面板

    image-20250208231342642

  3. 云对象日志面板

    当我们在云对象中使用console.log调试代码时,日志的输出在这个位置。

    image-20250208231411534

需要注意的是当我们修改了云对象的代码时,都需要重新点击 debug book

image-20250208231500267

部署云对象

当云对象开发完毕后,想要客户端调用或者上线生产环境,都需要部署上去。右键你的云对象,选择 Deploy 'book' 就可以把你的云对象部署到云端了。你也可以鼠标右键 cloudfouctions 部署或者同步所有的云对象。

如果想要把之前部署到云端的云对象下载下来,选择 Sync book 即可

image-20250209095250827

部署成功,右下角会有相应的提示。

image-20250209095502324

客户端调用云对象

当我们创建好了云对象后,就可以使用客户端直接调用云对象的方法了。

如果是客户端调用云函数,可以参考以下代码。

核心代码如下。

typescript
try {
  const res = await cloudFunction.call({
    // 云对象的名称,这个和你创建的云对象文件名一致
    name: "book",
    data: {
      // book云对象的methods1方法
      method: 'method1',
      // 传递给method1的参数
      params: ['a', 'b'],
    }
  })
  AlertDialog.show({ message: JSON.stringify(res, null, 2) })
} catch (e) {
  console.error(e.message + " " + e.code)
}

这里重点讲解的是客户端调用云对象

DevEco Studio 提供了方便的方式实现客户端调用云对象的功能。可以让我们想调用普通对象方法一样,直接调用云对象!

具体操作步骤如下。

  1. 依据云对象 ,在客户端生成云对象的调用模型。 鼠标右键你的云对象 Book。选择 Generate Invoke Interface

    image-20250209104450026

  2. 选择存放该模型文件的路径

    image-20250209104513583

  3. 生成成功后,得到两个关键文件 ImportObject.tsBook.ets

    image-20250209104549805

    ImportObject.ts文件的主要作用是利用代理Proxy的方式,当用户调用Book.method1 方法时,在内部执行了 云函数的调用方法cloudFunction.call方法。为我们调用云对象提供了便利。代码如下。

    typescript
    /*
     * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved.
     * Generated by the Cloud Object compiler. DO NOT EDIT!
     */
    import type { BusinessError } from '@kit.BasicServicesKit';
    import { cloudFunction } from '@kit.CloudFoundationKit';
    
    export interface CloudObjectLikely {
      name: string;
    }
    
    function mockMethod<T extends CloudObjectLikely>(target: T, version: string,
      prop: string | symbol): (...args: unknown[]) => Promise<unknown> {
      return async (...args: unknown[]) => new Promise((resolve, reject) => {
        cloudFunction.call({
          name: target.name,
          version: version,
          data: {
            method: prop,
            params: args
          }
        }).then((value: cloudFunction.FunctionResult) => {
          resolve(value.result);
        }).catch((err: BusinessError) => {
          reject(err);
        });
      });
    }
    
    export function importObject<T extends CloudObjectLikely>(tClass: new () => T, version = '$latest'): T {
      return new Proxy<T>(new tClass(), {
        get(target, prop): (...args: unknown[]) => Promise<unknown> {
          return mockMethod<T>(target, version, prop);
        }
      });
    }

    Book.ets文件的作用是参考云对象Book的模型,生成对应的类,方便我们调用云对象。代码如下。

    typescript
    /*
     * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved.
     * Generated by the Cloud Object compiler. DO NOT EDIT!
     */
    import type { CloudObjectLikely } from '../ImportObject';
    
    export class Book implements CloudObjectLikely {
        public name = 'book';
        public async method1(param1: any, param2: any): Promise<{
            simple: string;
            param1: any;
            param2: any;
        }> {
    
            return Promise.reject(new Error('Method not implemented.'));
        }
    }
  4. 最后,在客户端中需要导入 BookimportObject,再进行调用

    jsx
    // 这里需要对Book进行重命名,因为和之前的数据库类型Book重名了
    import { Book as BookCloudObj } from '../cloudobject/book/Book';
    import { importObject } from '../cloudobject/ImportObject';
    
          Button("调用云对象14的方法")
            .onClick(this.fn14)
    
        fn14 = async () => {
        try {
          let bookObj = importObject(BookCloudObj); // 使用importObject实例化BookCloudObj的代理
          const res = await bookObj.method1('100', '200')
          AlertDialog.show({ message: JSON.stringify(res, null, 2) })
        } catch (e) {
          console.error(e.message + " " + e.code)
        }
      }
  5. 调用结果如图。

    image-20250209105304376

云对象调用云对象

在实际开发中,一般情况下,某个具体的业务是存在某个具体的云对象中的,但是必不可少的会出现业务之间的关联。比如查询某一个作者编写过的所有的书籍。作者可以是一个单独的云对象,书籍也是一个单独的云对象,这样就会出现云对象之间调用云对象 需求。我们可以通过 book云对象调用auth云对象来演示。

由于云对象部署到云端后是各自独立的,因此这里不能简单的将多个云对象看成是同一个目录之间的关系,也就是不能 云对象a使用相对路径直接导入云对象b !

  1. 新建auth云对象 这个看上面的创建book云对象步骤即可

  2. 调整auth云对象的网关设置

    function-config.json 中的authType设置为 cloudgw-client

    json
    "authType": "cloudgw-client"
  3. 编写auth云对象逻辑代码

    js
    export class Auth {
      getName(firstName, lastName) {
        return { firstName, lastName, fullName: firstName + lastName };
      }
    }
  4. book安装云对象三方库,注意需要在book云对象目录下安装

    sh
    npm install @hw-agconnect/cloud-server

    image-20250209112440351

  5. book调用auth云对象

    js
    import { cloud } from "@hw-agconnect/cloud-server";
    
    export class Book {
      async method1(param1, param2) {
    
        const res = await cloud.function().call({
          // 云对象的名称
          name: "auth",
    
          data: {
            // 云对象的方法
            method: "getName",
            // 传递的参数
            params: ['万', "大妈"]
          }
        });
    
        return {
          "simple": "example",
          param1,
          param2,
          res
        };
      }
    }
  6. auth和book都部署到云端

  7. 客户端测试调用,验证效果

    image-20250209112607971

    支持,便完成了云对象调用云对象了。

云对象调用云存储

在翻阅了相关文档后,发现云对象对于云存储的支持程度远不够,这里略过。

云对象调用云数据库

云对象中也是需要引入 @hw-agconnect/cloud-server 来操作云数据库的。我们按照以下步骤进行操作。实现查询云对象中查询数据库的功能。

  1. 首先在云对象book处新建一个文件夹model,用来存放即将生成的文件

    image-20250209133623676

  2. 在云端生成操作Book表格的云对象模块。这里 DevEco Studio会自动帮我们生成。选择Generate Server Model

    image-20250209133103645

    然后选择生成的路径。

    image-20250209133521557

  3. 得到文件 model/book.ts ,这里为了和之前的book.ts区分,我们把它名称修改成 model/bookModel.ts。文件中的类名也修改成 BookModel

    typescript
    /*
     * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved.
     * Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
     */
    
    class BookModel {
      id: number;
      name: string;
      price: number;
      publish: Date;
      hot: boolean;
      cover: string;
    
      constructor() {
      }
    
      getFieldTypeMap(): Map<string, string> {
        let fieldTypeMap = new Map<string, string>();
        fieldTypeMap.set('id', 'Integer');
        fieldTypeMap.set('name', 'String');
        fieldTypeMap.set('price', 'Double');
        fieldTypeMap.set('publish', 'Date');
        fieldTypeMap.set('hot', 'Boolean');
        fieldTypeMap.set('cover', 'String');
        return fieldTypeMap;
      }
    
      getClassName(): string {
        return 'Book';
      }
    
      getPrimaryKeyList(): string[] {
        let primaryKeyList: string[] = [];
        primaryKeyList.push('id');
        return primaryKeyList;
      }
    
      getIndexList(): string[] {
        let indexList: string[] = [];
        indexList.push('id');
        indexList.push('price');
        return indexList;
      }
    
      getEncryptedFieldList(): string[] {
        let encryptedFieldList: string[] = [];
        return encryptedFieldList;
      }
    
      setId(id: number): void {
        this.id = id;
      }
    
      getId(): number {
        return this.id;
      }
    
      setName(name: string): void {
        this.name = name;
      }
    
      getName(): string {
        return this.name;
      }
    
      setPrice(price: number): void {
        this.price = price;
      }
    
      getPrice(): number {
        return this.price;
      }
    
      setPublish(publish: Date): void {
        this.publish = publish;
      }
    
      getPublish(): Date {
        return this.publish;
      }
    
      setHot(hot: boolean): void {
        this.hot = hot;
      }
    
      getHot(): boolean {
        return this.hot;
      }
    
      setCover(cover: string): void {
        this.cover = cover;
      }
    
      getCover(): string {
        return this.cover;
      }
    
      static parseFrom(inputObject: any): BookModel {
        let result = new BookModel();
        if (!inputObject) {
          return result;
        }
        if (inputObject.id) {
          result.id = inputObject.id;
        }
        if (inputObject.name) {
          result.name = inputObject.name;
        }
        if (inputObject.price) {
          result.price = inputObject.price;
        }
        if (inputObject.publish) {
          result.publish = new Date(inputObject.publish);
        }
        if (inputObject.hot) {
          result.hot = inputObject.hot;
        }
        if (inputObject.cover) {
          result.cover = inputObject.cover;
        }
        return result;
      }
    }
    
    export { BookModel };
  4. 新建文件bookController.ts,负责组合 对book实例的CURD操作。

    typescript
    import { cloud, CloudDBCollection } from '@hw-agconnect/cloud-server'; //引入Server SDK依赖
    import { BookModel } from './model/BookModel'; //BookModel为对象类型名
    
    // ZONE_NAME为存储区名称
    const ZONE_NAME = "Study";
    
    export class BookCtroller {
      collection: CloudDBCollection<BookModel>;
    
      constructor() {
        this.collection = cloud.database({ zoneName: ZONE_NAME }).collection(BookModel);
      }
    
      //查询数据
      async queryBooks() {
        let query = this.collection.query();
        return await query.get();
      }
    
      //更新数据
      async upsertBooks(records: BookModel[]) {
        return await this.collection.upsert(records);
      }
    
      //删除数据
      async deleteBooks(records: BookModel[]) {
        return await this.collection.delete(records);
      }
    }
  5. 最后在云对象book中添加一个查询方法 query。

    typescript
      // 查询数据库
      async query() {
        let bookCtroller = new BookCtroller();
        const result = await bookCtroller.queryBooks()
        return {
          result
        }
      }
  6. 测试调用

    image-20250209140233851

其他的数据库操作可以基于该模式进行拓展。

云对象调用第三方api

如想要调用其他的api,比较简单的方式可以直接安装axios,然后像调用接口一样使用axios即可。


总结

本文完整介绍了HarmonyOS Next端云一体化开发的各个方面,包括:

端云一体化基础

  • Cloud Foundation Kit的基本概念和应用场景
  • AGC平台和DevEco Studio的环境搭建
  • 云函数、云数据库、云存储三大核心资源

云数据库操作

  • 存储区和对象类型的创建与配置
  • 对象类型的规范(objectTypeName、fields、indexes、permissions)
  • 数据条目的添加和部署流程

客户端操作云数据库

  • 四种操作方式的对比
  • 客户端数据模型的自动生成
  • 基本CRUD操作:query、upsert、delete、calculateQuery

数据库查询条件

  • 谓词的概念和使用
  • 丰富的查询条件:比较、字符串匹配、逻辑组合等
  • 实际应用示例

云存储操作

  • 云存储的计费策略
  • 核心功能:上传、下载、列表、元数据、删除
  • 文件操作的完整流程

云函数开发

  • 云函数vs云对象的对比
  • 云对象的创建、调试、部署
  • 客户端调用云对象的方式
  • 云对象之间的相互调用
  • 云对象操作云数据库

通过本教程的学习,开发者可以全面掌握HarmonyOS Next端云一体化开发的核心技术,为构建高效、安全的应用提供完整的解决方案。


参考文档

  1. 云侧通过SDK使用云数据库
  2. @hw-agconnect/cloud-server
  3. 端云一体化开发

如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯,欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。

Released under the MIT License.