HarmonyOS Next 端云一体化完整教程
目录
端云一体化基础
介绍
Cloud Foundation Kit(云开发服务)可以按需为应用提供云函数、云数据库、云存储等云端服务。应用运行所需的服务器和环境可以皆由云端平台提供,开发者只需关注应用的业务逻辑,而无需关心基础设施(例如:服务器、操作系统、容器等)。
DevEco Studio 中还提供了端云一体化开发的开发体验,您可以基于统一的技术栈,高效、协同地完成端、云代码的编写、调试、编译和部署,极大提高构建 HarmonyOS 应用和元服务的效率。
应用场景
- 应用后端 快速构建应用或者元服务的后端服务,从而大幅简化应用开发与运维相关的事务,快速完成应用的构建
- 计算密集型任务 当应用中出现计算密集型任务时,可以在云端及时申请足够的算力来支撑任务的执行。当任务结束时,可以立即释放资源,避免浪费。
- 适配类应用 通过 Cloud Foundation Kit 实现协议类型的转换,比如实现 IoT 中不同设备的协议适配。以及接入第三方平台,通过第三方平台提供的接口,实现业务的接入或者协作。
- 突发大量访问 传统架构服务在某些特殊场景下,可能出现大量的访问。为保证业务高峰时,系统能稳定运行,一般需要购买高性能、昂贵的服务器,组建集群负载均衡。但是,当业务回落时,就导致了大量服务器的资源浪费。
包含的资源
端云一体主要包含的资源有
- 云函数
- 云数据库
- 云存储
适配的项目类型
端云一体可以用在开发鸿蒙的应用或者是元服务上。因为考虑应用的上架需要软著+备案,而元服务的上架只需要备案。所以后续的教程中为了更加方便学习,我们会在元服务的环境下进行。
学习流程
稍后我们会延续着以下步骤,让大家可以尽可能的掌握端云一体的开发流程。

- AGC 平台新建元服务
- DevEco Studio 新建对应的项目工程
- 云函数的基本使用
- 云数据库的基本使用
- 云存储的基本使用
AGC 平台新建元服务
在 AGC 平台上新建服务的具体步骤可以参考这个文章 HarmonyOS Next 最新 元服务新建到上架全流程,这里就不再叙述了。
DevEco Studio 新建对应的项目工程
本地在使用 DevEco Studio 新建工程时,需要新建的是端云一体化的元服务项目。

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

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

- 测试下云函数

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

测试下云存储

云端环境一览
我们需要知道的是,在本地操作云端的资源时,本质上都是对云端的资源的一些操作。最终的操作成功与否,都会反映到真正的云端环境。
我们登录 AGC 平台,然后找到对应的项目。这里是元服务的名称,不是项目的名称,你的元服务是归属于某个项目的

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

云数据库一览

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

存储区

数据

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

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

它里面的主要目录结构如下。
- clouddb 存放数据库相关的文件
- cloudfunctions 存放云函数相关的文件
- cloud-config.json 存放云端环境相关的配置
后期我们开发的思路可以是以下两种方式:
- 全部都在 AGC 平台上操作,如编写云函数逻辑代码、搭建数据库、数据表
- 尽量都在本地 DevEco Studio 工具中编写云函数逻辑代码、调试代码、搭建数据库、数据集表
以上两种方式都可以。但是为了刚好的开发体验,我们是建议使用方式 2.
云数据库操作
介绍
云数据库是端云协同的数据库产品,具备端云数据协同管理、统一数据模型及丰富数据管理 API 接口等能力。它采用基于对象模型 的数据存储结构,数据以对象(Object) 形式存于不同存储区,每个对象为一条完整数据记录。 对象类型(ObjectType) 定义存储对象集合,不同对象类型对应不同数据结构。存储区(Zone) 是独立的数据存储区域,每个存储区的对象类型定义完全相同。
学习流程
我们会按照以下流程来进行学习。
- 新建存储区:也就是新建数据库
- 创建对象类型:创建一个用于存储数据条目的对象类型。
- 添加数据条目:在刚刚创建的对象类型内添加一条条数据,并配置数据所在的存储区。
- 部署云数据库:数据成功添加后,您可以直接将该数据部署至 AGC 云端。您也可以等所有对象类型和数据条目开发完成后,再统一批量部署到 AGC 云端。
存储区
我们这里新建一个存储区 Study 用来存放稍后用到的数据。

然后我们在 DevEco Studio 的 云端配置中指定存储区。
clouddb/db-config.json
{
"defaultCloudDBZoneName": "Study", // 存储区的名称
"defaultDataStorageLocation": "CN"
}创建对象类型
什么是创建对象类型
创建对象类型可以理解为就是为我们的数据定义类型。类似于使用接口或者 class 来定义数据,但是这里操作的范围要更大。
- 定义基本的数据类型,如数字、字符串、布尔、日期时间等。
- 定义字段的权限,因为这个对象类型本身是数据表,定义权限相当于设置了哪一类用户拥有哪些权限。如读取、编辑、新增、删除等
- 定义索引,考虑数据量大需要比较方便的查找到要操作的数据,需要定义索引。
比如,我们需要根据书籍数据来定义类型。 Book
| 字段 | 类型 | 说明 |
|---|---|---|
| id | number | id |
| name | string | 书名 |
| price | number | 价格 |
| publish | date | 出版日期 |
| hot | boolean | 是否热门 |
| cover | string | 封面 |
对象类型的规范
新建 Book 对象类型文件

这个对文件需要按照以下格式来编写。其中的字段的一些规范需要特别注意。
{
"objectTypeName": 对象实体名称
"fields": 对象中的属性
"indexes": 索引
"permissions" 权限
}objectTypeName
表示实体对象的名称,我们这里可以是 Book
{
"objectTypeName": "Book"
}fields
fields 表示这个对象中属性的一些规则。
| 参数 | 必选(M)/可选(O) | 说明 |
|---|---|---|
| fieldName | M | 字段名称。输入要求具体如下:字段的名称长度必须大于或等于 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。 |
| fieldType | M | 字段的数据类型。当前支持的数据类型:String、Boolean、Byte、Short、Integer、Long、Float、Double、ByteArray、Text、Date、IntAutoIncrement(数字-自增) |
| belongPrimaryKey | O | 设置该字段是否为对象类型的主键,默认值为 false。至少设置一个字段为主键。支持设置复合主键,由多个字段组合成为主键,一个复合主键包含的字段小于等于 5 个,复合主键字段顺序与字段的顺序一致。数据类型为 ByteArray、Text、Date、Double、Float 和 Boolean 的字段不支持设置为主键。主键的值不允许更改。 |
| notNull | O | 设置字段值是否为非空,默认值为 false。数据类型为 ByteArray 和 Date 的字段不支持设置为非空。主键默认非空,且不允许更改。设置为非空的字段不支持加密和敏感。 |
| isNeedEncrypt | O | 设置字段是否需要加密,开启全程加密数据管理功能,默认值为 false。选择加密后,该字段对应的数据会加密存储在存储区中。主键字段不支持加密。加密的字段不支持设置为非空。加密的字段不支持设置为敏感字段。一个对象类型中包含的加密字段和敏感字段的总数需小于或等于 5 个。字段设置为加密后,不支持导出该字段的数据值。数据类型为 ByteArray、Text 的字段不支持加密。对象类型创建成功后,不支持修改加密属性。 |
| isSensitive | O | 设置字段是否为敏感字段,默认值为 false。选择敏感后,该字段对应的数据会加密存储在存储区中。敏感字段不支持设置为主键。敏感字段不支持设置为非空。敏感字段不支持设置为加密。敏感字段不支持设置为默认值。对象类型创建成功后,不支持修改敏感属性。仅支持数据类型为 Byte、Short、Integer、Long、Float、Double、String 和 Date 的字段设置为敏感字段。敏感字段不支持设置为索引。一个对象类型中包含的加密字段和敏感字段的总数需小于或等于 5 个。 |
| defaultValue | O | 字段为非空时,必须设置默认值。主键不支持设置默认值。加密字段和敏感字段不支持设置默认值。数据类型为 ByteArray、Date 不支持为其设置默认值。数据类型为 Text 的字段设置默认值时,默认值的长度小于或等于 200 个字符。 |
按照我们想要的书籍的规定。可以这样
| fieldName | fieldType | belongPrimaryKey |
|---|---|---|
| id | IntAutoIncrement | true |
| name | String | - |
| price | Double | - |
| publish | Date | - |
| hot | Boolean | - |
| cover | String | - |
{
"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) | 说明 |
|---|---|---|
| indexName | M | 索引名称。输入要求具体如下:索引的名称长度必须大于或等于 1 个字符,小于或等于 30 个字符,只能包含以下 3 种类型,并且至少包含"字母"类型:字母(A-Z 或 a-z)数字(0-9)特殊字符:_索引名称必须以字母开头。索引名称中不区分字母的大小写。修改对象类型时,仅支持新增或者删除索引。当删除索引后,本次提交前不允许新增同名索引。每个对象类型可以设置小于或等于 16 个索引。数据类型为 ByteArray 和 Text 的字段不支持设置为索引。 |
| indexList > fieldName | M | 索引包含的字段。支持设置组合索引,由多个字段组合成为索引,一个组合索引包含的字段不超过 5 个。 |
| indexList > sortType | M | 索引包含的字段的排序方式,支持 ASC 升序或 DESC 降序。 |
这里我们为书籍的 id 和书籍的名称提供索引。
{
"indexes": [
{
"indexName": "id_Index",
"indexList": [
{
"fieldName": "id",
"sortType": "ASC"
}
]
},
{
"indexName": "price_Index",
"indexList": [
{
"fieldName": "price",
"sortType": "DESC"
}
]
}
]
}permissions
permissions 字段用来设置该数据表的操作权限的。
目前提供的角色和权限有以下分类。
| 参数 | 必选(M)/可选(O) | 说明 |
|---|---|---|
| role | M | 用户角色,包括: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 可以管理并配置其他角色的权限。 |
| rights | M | 授予角色的权限,包括 Read、Upsert(包含新增和修改)和 Delete 权限。 |
接下来,我们为 这些角色设置以下的权限。
| 角色 | Read | Upsert | Delete |
|---|---|---|---|
| World | √ | – | – |
| Authenticated | √ | √ | – |
| Creator | √ | √ | √ |
| Administrator | √ | √ | √ |
代码如下:
{
"permissions": [
{
"role": "World",
"rights": ["Read"]
},
{
"role": "Authenticated",
"rights": ["Read", "Upsert"]
},
{
"role": "Creator",
"rights": ["Read", "Upsert", "Delete"]
},
{
"role": "Administrator",
"rights": ["Read", "Upsert", "Delete"]
}
]
}创建数据条目
我们搭建好了对象类型,现在可以根据它来插入数据了。
新建数据文件


插入数据 这里可以看到一些基本数据
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 平台的数据库上。
部署的方式也很简单
- 部署

- 成本

刷新 AGC 平台上的数据库

客户端操作云数据库
上一章我们主要讲解了如何新建数据库、新建数据表已经部署数据库。这一章主要学习如何对数据库、数据表进行 CRUD 的操作。
操作数据库的方式
我们操作数据库的方式一共有 4 种。
- 可视化 - AGC 平台上直接编辑数据
- 可视化 - DevEco Studio 中直接编辑数据
- 编程 - 客户端通过代码的方式操作数据
- 编程 - 云函数通过代码的方式操作数据
方式 1、2 都是为了让开发人员简单、方便管理数据。但是实际的业务场景中,我们更多要关注的是 3、4 的方式。那么本章主要讲的是 方式 3-客户端通过代码的方式操作数据 。后续再讲到云函数的时候再来补充方式 4。
生成客户端-数据模型
先解释下这个功能是做什么的。因为我们的目标是要在 客户端来查询数据库的数据,那必不可少需要在客户端中定义数据表实体的类型。然后 DevEco Studio 提供了比较便捷的根据数据实体生成客户端-数据模型。

我这里红色的提示是因为我之前已经生成过了,所以提示是否覆盖。
成功后边得到如下内容:entry/src/main/ets/common/types/Book.ts
/*
* 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 };简单使用
接下来我们就可以进入客户端查询数据库的步骤了。
- 首先我们需要创建一个数据库示例,每一个存储区就是一个数据库
cloudDatabase.DatabaseZone - 然后指定查询条件,比如全部查询、查询 id 等于 1 等等
condition - 进行查询,接收返回的数据
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%')
}
}
对数据表的操作
端云一体提供了基本的对数据表的操作。主要分成以下几种
| 操作类型 | 说明 |
|---|---|
| query | 查询 |
| upsert | 新增或者编辑 |
| delete | 删除 |
| calculateQuery | 计算 |
query
就是查询,上面的示例中已经使用过了。
upsert - 新增
现在主要演示 使用 upsert 实现新增
upsert 方法可以接收一个或者多个数据实体。如果该数据之前不存在,这时的 upsert 表示新增,反之表示更新。
操作成功后,会返回成功影响了的数据的数量。
比如新增:
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);
需要注意的是:我们当前的角色是 World,此时是没有 新增、编辑、删除权限的。所以为了方便操作,可以修改调整权限
clouddb/objecttype/Book.json
{
"role": "World",
"rights": [
"Read",
"Upsert",
"Delete"
]
},当数据表信息发生了修改时,需要在 AGC 平台上删除之前的数据区+数据表。然后重新部署。
upsert - 编辑
这里我们可以根据 id 来编辑一下数据。 数据库里面存放着id:10 的数据,我们就来修改它。

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 的元素执行删除。
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 从数据库中查询符合条件的数据,并对指定字段进行算术计算。主要提供了以下的计算功能。
| 名称 | 值 | 说明 |
|---|---|---|
| AVERAGE | 0 | 计算平均数。 |
| SUM | 1 | 计算总和。 |
| MAXIMUM | 2 | 计算最大值。 |
| MINIMUM | 3 | 计算最小值。 |
| COUNT | 4 | 计算记录总数。 |

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的数据
this.condition.equalTo("id", 20)notEqualTo 查询id不等于20的数据
this.condition.notEqualTo("id", 20)beginsWith 查询name字段以b开头的数据
this.condition.beginsWith("name", "b")endsWith 查询name字段以k结尾的数据
this.condition.endsWith("name", "k")contains 查询name字段包含k的数据
this.condition.contains("name", "k")greaterThan 查询price字段大于30的数据
this.condition.greaterThan("price", 30)greaterThanOrEqualTo 查询price字段大于或者等于30的数据
this.condition.greaterThanOrEqualTo("price", 30)lessThan 查询price字段小于30的数据
this.condition.lessThan("price", 30)lessThanOrEqualTo 查询price字段小于或者等于30的数据
this.condition.lessThanOrEqualTo("price", 30)in 查询name字段包含在["book","aaaa","bbbb"]的中数据
this.condition.in("name", ["book", "aaaa", "bbbb"])isNull 查询name字段是否为null
this.condition.isNull("name")isNotNull 查询name字段是否非null
this.condition.isNotNull("name")orderByAsc 根据id,进行升序
this.condition.orderByAsc("id")orderByDesc 根据id,进行降序
this.condition.orderByDesc("id")limit 查询2条数据,从第1条开始
this.condition.limit(2, 1)or 逻辑或,查询name=book 或者 price>30的数据
this.condition.equalTo("name", "book").or().greaterThan('price', 30)and 逻辑与,查询name=book123 并且 price>30的数据
this.condition.equalTo("name", "book123").and().greaterThan('price', 30)beginGroup 和 endGroup 表示一对逻辑分组
// 条件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平台上开通云存储环境。

我们可以看到,这个的云存储的实例名称为 default-bucket-xxxx
初始化云存储实例
因为后期要操作云存储都需要用到云存储实例。所以需要初始化好。
使用默认云存储实例
bucket: cloudStorage.StorageBucket = cloudStorage.bucket(); // 获取默认的存储实例指定云存储实例
bucket: cloudStorage.StorageBucket = cloudStorage.bucket("default-bucket-xxxx"); // 获取默认的存储实例上传文件到云端
上传文件到云端只能调用StorageBucket.uploadFile方法,但是该方法要求上传的文件路径必须存放在context.cacheDir目录下。因此需要先提前做好这个处理。
步骤:
- 选择待上传的文件,下方示例代码中使用photoAccessHelper.PhotoViewPicker指定需要上传的文件。
- 将待上传的文件复制到context.cacheDir目录下。
- 调用StorageBucket.uploadFile接口创建上传任务,监听上传任务的progress、completed、failed等事件。
- 启动上传任务。
uploadFile在上传文件时,还支持上传自定义的标准的http头部信息。具体可以查看API说明
示例代码:
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-中的云存储: 可以看到文件成功上传了。

查看云端文件列表
如果想要获取云端文件列表,可以使用 StorageBucket.list API。
参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 |
| options | ListOptions | 否 | 列举操作的相关参数。 |
ListOptions
| 名称 | 类型 | 只读 | 可选 | 说明 |
|---|---|---|---|---|
| maxResults | number | 否 | 是 | 列举文件的最大数量,取值范围1-1000,默认则列举所有文件。 |
| pageMarker | string | 否 | 是 | 分页标识。 |
返回内容
| 名称 | 类型 | 只读 | 可选 | 说明 |
|---|---|---|---|---|
| directories | string[] | 否 | 否 | 列举操作返回的云侧目录列表。 |
| files | string[] | 否 | 否 | 列举操作返回的云侧文件列表。 |
| pageMarker | string | 否 | 是 | 分页标识。 |
示例代码
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)得到结果

查看云端文件元数据
我们可以通过该StorageBucket.getMetadataAPI获取到文件名、文件大小、文件类型等常用属性,也包括用户自定义的文件属性。
参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 |
返回值
| 类型 | 说明 |
|---|---|
| Promise<Metadata> | Promise对象,返回云侧文件的元数据信息。 |
示例代码
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)得到结果

获取云端文件下载地址
我们之前通过getMetadata获取到了云端文件的相关信息。但是如果该文件是图片,而我们想要使用Image显示该图片,那么还需要使用StorageBucket.getDownloadURL获取到该文件的下载地址。
参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 |
返回值
类型 | 说明 |
|---|---|
Promise<string> | Promise对象,返回云侧文件下载地址。 |
示例代码
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)得到结果

下载云端文件到本地
利用 StorageBucket.downloadFile 可以将文件下载到 ontext.cacheDir 目录下。
参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| context | common.BaseContext | 是 | 应用上下文。 |
| parameters | DownloadParams | 是 | 下载相关参数。 |
DownloadParams
| 名称 | 类型 | 只读 | 可选 | 说明 |
|---|---|---|---|---|
| localPath | string | 否 | 否 | 本地文件路径,根路径为cache目录。 |
| cloudPath | string | 否 | 否 | 云侧文件路径。 |
| mode | request.agent.Mode | 否 | 是 | 下载任务类型,前端任务在应用切换到后台一段时间后失败/暂停;后台任务不受影响。默认为BACKGROUND。BACKGROUND:后台任务。FOREGROUND:前端任务。 |
| overwrite | boolean | 否 | 是 | 当本地文件已存在时,是否覆盖本地文件,默认false。true:覆盖本地文件。false:不覆盖,若存在同名文件则下载失败。 |
| network | request.agent.Network | 否 | 是 | 下载任务的网络配置,网络不满足设置条件时,未执行的任务等待执行,执行中的任务失败/暂停。默认为ANY。ANY:不限网络类型。WIFI:无线网络。CELLULAR:蜂窝数据网络。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<[Task]> | Promise对象,返回下载任务。 |
示例代码
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)得到结果

删除云端文件
调用StorageBucket.deleteFile删除云侧的文件。
参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| cloudPath | string | 是 | 云侧文件路径。 |
返回值
| 类型 | 说明 |
|---|---|
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}` })
}
}得到结果

云函数开发
之前的文章中把云数据库、云存储都讲过了,这一章节要讲解的是云函数。
云函数介绍
云函数其实就是serverless技术。可以理解云函数就实现传统后端中的具体业务,而无需关心服务器购买、部署、安全、性能等一系列相关问题,专注于具体的业务开发。HarmonyOS Next的云函数采用的是typescript的语法,这对熟悉js或者熟悉ArkTs的同学来说都很容易上手。
另外云函数具有调用其他云函数、调用第三方接口、调用云存储、调用云数据库的能力。对于段云一体化开发的应用来说,可以根据这样的场景来使用云函数。
- 简单的数据库查询、云端文件的管理可以直接使用客户端操作的方式。
- 繁琐或者涉及安全的操作,可以把业务抽离到云函数端,这样更加容易管理项目。
本章节也会讲解如何云函数的开发、创建、调试、部署,以及在云函数端调用其他云函数、调用第三方接口、调用云存储、调用云数据库。
创建云函数
可以选择创建云函数或者云对象。这里建议选择云对象更加方便业务逻辑的实现。为什么呢,举个例子。书籍的crud刚好放在一个对象中,增、删、改、查都可以做个一个对象的属性存在,更加容易方便管理。比如以下示例。
云函数一览
typescript// 云函数 let myHandler = async function (event, context, callback, logger) { // 这里写不同的业务逻辑,都是都耦合在一个myHandler中 callback(); }; export { myHandler }云对象一览
typescriptexport class Book { // 增加 add() { return {}; } // 删除 delete() { } // 修改 update() { } // 查询 query() { } }
新建云对象,云对象名称为book。

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

function-config.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。 authType HTTP触发器的认证类型。apigw-client:端侧网关认证,适用于来自APP客户端侧(即本地应用或者项目)的函数调用。cloudgw-client:云侧网关认证,适用于来自APP服务器侧(即云函数)的函数调用。
oh-package.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的文件解析如下。
- methods1表示云对象具体的方法,可以有多个。
- params1、params2是传递给该云对象的参数
- return 的内容是返回给云对象的调用者

调试云函数/对象
在开发云对象时,经常需要编写繁琐的业务逻辑,那么就必不可少对象的调试。DevEco Studio 内置了云对象的调试面板。按照以下步骤操作就可以方便对云对象进行调试了。
启动云对象调试,鼠标右键你要调试的云对象、选择 Debug 'book'

打开云对象调试面板

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

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

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

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

客户端调用云对象
当我们创建好了云对象后,就可以使用客户端直接调用云对象的方法了。
如果是客户端调用云函数,可以参考以下代码。
核心代码如下。
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 提供了方便的方式实现客户端调用云对象的功能。可以让我们想调用普通对象方法一样,直接调用云对象!
具体操作步骤如下。
依据云对象 ,在客户端生成云对象的调用模型。 鼠标右键你的云对象 Book。选择 Generate Invoke Interface

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

生成成功后,得到两个关键文件
ImportObject.ts和Book.ets
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.')); } }最后,在客户端中需要导入
Book和importObject,再进行调用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) } }调用结果如图。

云对象调用云对象
在实际开发中,一般情况下,某个具体的业务是存在某个具体的云对象中的,但是必不可少的会出现业务之间的关联。比如查询某一个作者编写过的所有的书籍。作者可以是一个单独的云对象,书籍也是一个单独的云对象,这样就会出现云对象之间调用云对象 需求。我们可以通过 book云对象调用auth云对象来演示。
由于云对象部署到云端后是各自独立的,因此这里不能简单的将多个云对象看成是同一个目录之间的关系,也就是不能 云对象a使用相对路径直接导入云对象b !
新建auth云对象 这个看上面的创建book云对象步骤即可
调整auth云对象的网关设置
function-config.json中的authType设置为cloudgw-clientjson"authType": "cloudgw-client"编写auth云对象逻辑代码
jsexport class Auth { getName(firstName, lastName) { return { firstName, lastName, fullName: firstName + lastName }; } }book安装云对象三方库,注意需要在book云对象目录下安装
shnpm install @hw-agconnect/cloud-server
book调用auth云对象
jsimport { 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 }; } }auth和book都部署到云端
客户端测试调用,验证效果

支持,便完成了云对象调用云对象了。
云对象调用云存储
在翻阅了相关文档后,发现云对象对于云存储的支持程度远不够,这里略过。
云对象调用云数据库
云对象中也是需要引入 @hw-agconnect/cloud-server 来操作云数据库的。我们按照以下步骤进行操作。实现查询云对象中查询数据库的功能。
首先在云对象book处新建一个文件夹model,用来存放即将生成的文件

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

然后选择生成的路径。

得到文件
model/book.ts,这里为了和之前的book.ts区分,我们把它名称修改成model/bookModel.ts。文件中的类名也修改成BookModeltypescript/* * 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 };新建文件
bookController.ts,负责组合 对book实例的CURD操作。typescriptimport { 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); } }最后在云对象book中添加一个查询方法 query。
typescript// 查询数据库 async query() { let bookCtroller = new BookCtroller(); const result = await bookCtroller.queryBooks() return { result } }测试调用

其他的数据库操作可以基于该模式进行拓展。
云对象调用第三方api
如想要调用其他的api,比较简单的方式可以直接安装axios,然后像调用接口一样使用axios即可。
总结
本文完整介绍了HarmonyOS Next端云一体化开发的各个方面,包括:
端云一体化基础
- Cloud Foundation Kit的基本概念和应用场景
- AGC平台和DevEco Studio的环境搭建
- 云函数、云数据库、云存储三大核心资源
云数据库操作
- 存储区和对象类型的创建与配置
- 对象类型的规范(objectTypeName、fields、indexes、permissions)
- 数据条目的添加和部署流程
客户端操作云数据库
- 四种操作方式的对比
- 客户端数据模型的自动生成
- 基本CRUD操作:query、upsert、delete、calculateQuery
数据库查询条件
- 谓词的概念和使用
- 丰富的查询条件:比较、字符串匹配、逻辑组合等
- 实际应用示例
云存储操作
- 云存储的计费策略
- 核心功能:上传、下载、列表、元数据、删除
- 文件操作的完整流程
云函数开发
- 云函数vs云对象的对比
- 云对象的创建、调试、部署
- 客户端调用云对象的方式
- 云对象之间的相互调用
- 云对象操作云数据库
通过本教程的学习,开发者可以全面掌握HarmonyOS Next端云一体化开发的核心技术,为构建高效、安全的应用提供完整的解决方案。
参考文档
如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯,欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。