欢迎来到鸿蒙端云一体化开发教程的第三篇。前两篇我们分别学习了云函数和云数据库,今天咱们来聊聊应用开发中另一个非常重要的功能------云存储。
在现代应用中,我们经常需要处理用户上传的图片、视频或者文档。如果自己搭建文件服务器,不仅要考虑带宽、存储容量,还要操心CDN加速、安全防护等一堆麻烦事。Cloud Foundation Kit的云存储服务就是为了解决这些问题而生的。它提供了一个可扩展、安全可靠的云端存储服务,让你在端侧就能轻松实现文件的上传、下载和管理。
一、实战场景:图片上传与展示
为了让大家快速上手,我们今天模拟一个最常见的场景:用户上传头像并展示。
我们需要实现以下功能:
- 用户从相册选择一张图片。
- 将图片上传到云存储。
- 获取图片的下载地址进行展示。
- 不再需要时删除云端图片。
二、准备工作与初始化
2.1 申请权限
首先,云存储需要网络权限。打开entry/src/main/module.json5文件,添加网络权限:
json
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
2.2 初始化全局应用上下文
云存储在上传下载时需要用到应用的上下文(Context)。为了方便在各个地方使用,建议封装一个全局Context工具类。
在entry/src/main/ets/common目录下新建GlobalContext.ets:
typescript
import { common } from '@kit.AbilityKit';
export class GlobalContext {
private static context: common.UIAbilityContext;
public static initContext(context: common.UIAbilityContext): void {
GlobalContext.context = context;
}
public static getContext(): common.UIAbilityContext {
return GlobalContext.context;
}
}
然后,在你的EntryAbility.ets文件的onCreate方法中进行初始化:
typescript
import { GlobalContext } from '../common/GlobalContext';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
// 初始化全局上下文
GlobalContext.initContext(this.context);
}
// ... 其他代码
}
2.3 初始化存储实例
在需要使用云存储的页面或组件中,我们需要先获取存储实例(StorageBucket)。
typescript
import { cloudStorage } from '@kit.CloudFoundationKit';
// 使用默认实例,建议只初始化一次
let storageBucket: cloudStorage.StorageBucket = cloudStorage.bucket();
三、核心功能实现
3.1 上传文件
上传文件的流程通常是:
- 使用
PhotoViewPicker选择图片。 - 将图片复制到应用的缓存目录(因为云存储接口只支持上传缓存目录下的文件)。
- 调用
uploadFile接口上传。
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Component
export struct UploadPage {
// 1. 选择图片
async selectAndUpload() {
try {
let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = 1;
let photoViewPicker = new photoAccessHelper.PhotoViewPicker();
let photoSelectResult = await photoViewPicker.select(photoSelectOptions);
if (photoSelectResult.photoUris.length > 0) {
let fileUri = photoSelectResult.photoUris[0];
let fileName = fileUri.split('/').pop() as string;
// 构造缓存目录下的路径
let cacheFile = GlobalContext.getContext().cacheDir + '/' + fileName;
// 2. 复制文件到缓存目录
this.copyFile(fileUri, cacheFile);
// 3. 上传到云端,路径为 "avatar/文件名"
this.uploadFile(cacheFile, `avatar/${fileName}`);
}
} catch (err) {
hilog.error(0x0000, 'testTag', `选择图片失败: ${err.message}`);
}
}
copyFile(srcPath: string, dstPath: string) {
let srcFile = fs.openSync(srcPath);
let dstFile = fs.openSync(dstPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.copyFileSync(srcFile.fd, dstFile.fd);
fs.closeSync(srcFile);
fs.closeSync(dstFile);
}
uploadFile(localPath: string, cloudPath: string) {
storageBucket.uploadFile(GlobalContext.getContext(), {
localPath: localPath,
cloudPath: cloudPath
}).then((task: request.agent.Task) => {
// 监听上传进度
task.on('progress', (progress) => {
hilog.info(0x0000, 'testTag', `上传进度: ${JSON.stringify(progress)}`);
});
task.on('completed', () => {
hilog.info(0x0000, 'testTag', `上传完成!`);
// 上传完成后删除本地缓存文件
fs.unlink(localPath);
});
task.on('failed', (err) => {
hilog.error(0x0000, 'testTag', `上传失败: ${JSON.stringify(err)}`);
});
// 启动任务
task.start((err: BusinessError) => {
if (err) {
hilog.error(0x0000, 'testTag', `启动任务失败: ${err.message}`);
}
});
}).catch((err: BusinessError) => {
hilog.error(0x0000, 'testTag', `创建任务失败: ${err.message}`);
})
}
build() {
// ... UI代码
}
}
3.2 获取下载地址
上传成功后,我们需要获取图片的URL来展示。
typescript
async getImageUrl(cloudPath: string) {
try {
let downloadURL = await storageBucket.getDownloadURL(cloudPath);
hilog.info(0x0000, 'testTag', `图片地址: ${downloadURL}`);
// 这里可以将downloadURL保存到状态变量中,在Image组件中显示
return downloadURL;
} catch (err) {
hilog.error(0x0000, 'testTag', `获取地址失败: ${err.code}, ${err.message}`);
}
}
3.3 下载文件到本地
有时候我们需要把文件下载下来保存到本地,而不是仅仅获取URL。
typescript
downloadFile(cloudPath: string, localFileName: string) {
storageBucket.downloadFile(GlobalContext.getContext(), {
cloudPath: cloudPath,
localPath: localFileName // 文件将保存在context.cacheDir目录下
}).then((task: request.agent.Task) => {
task.on('completed', () => {
hilog.info(0x0000, 'testTag', `下载完成`);
});
task.start((err) => {
if (err) {
hilog.error(0x0000, 'testTag', `启动下载失败: ${err.message}`);
}
});
});
}
3.4 删除文件
如果用户更换了头像,旧头像可以删除以节省空间。
typescript
async deleteCloudFile(cloudPath: string) {
try {
await storageBucket.deleteFile(cloudPath);
hilog.info(0x0000, 'testTag', `删除成功`);
} catch (err) {
hilog.error(0x0000, 'testTag', `删除失败: ${err.code}, ${err.message}`);
}
}
3.5 获取文件列表
如果你想做一个相册功能,展示所有上传的图片,可以使用list接口。
typescript
async listFiles() {
try {
// 获取avatar目录下的文件
let result = await storageBucket.list('avatar/');
hilog.info(0x0000, 'testTag', `文件列表: ${JSON.stringify(result)}`);
// result.files 包含了所有文件名
} catch (err) {
hilog.error(0x0000, 'testTag', `获取列表失败: ${err.code}, ${err.message}`);
}
}
四、元数据管理
元数据(Metadata)就是"关于数据的数据",比如文件大小、类型,或者你自定义的标签。
4.1 设置元数据
比如我们想给上传的图片打个标签:
typescript
async setFileMetadata(cloudPath: string) {
try {
await storageBucket.setMetadata(cloudPath, {
customMetadata: {
category: "portrait",
tag: "hd"
}
});
hilog.info(0x0000, 'testTag', `设置元数据成功`);
} catch (err) {
hilog.error(0x0000, 'testTag', `设置失败: ${err.message}`);
}
}
4.2 获取元数据
下载前可以先看看文件多大,或者查看自定义标签:
typescript
async getFileMetadata(cloudPath: string) {
try {
let metadata = await storageBucket.getMetadata(cloudPath);
hilog.info(0x0000, 'testTag', `文件大小: ${metadata.size}, 类型: ${metadata.contentType}`);
} catch (err) {
hilog.error(0x0000, 'testTag', `获取元数据失败: ${err.message}`);
}
}
五、常见问题排查
在使用云存储时,你可能会遇到以下错误,这里有几个锦囊:
-
404 Product does not exist
- 原因:云存储服务没开通。
- 解决:去AGC控制台开通云存储服务。
-
403 Forbidden
- 原因:权限问题,通常是签名不对或者没登录。
- 解决 :
- 检查应用的签名方式(推荐使用自动签名)。
- 如果是需要登录的场景,确保已经通过
AuthProvider获取了用户凭据。
-
404 Not Found (初始化时)
- 原因:指定的存储实例名称不对。
- 解决 :检查
cloudStorage.bucket('name')里的名称是否和云端一致。
六、总结
云存储为应用提供了强大的文件管理能力,结合前两篇的云函数和云数据库,你已经具备了开发一个完整全栈应用的核心能力。
下一篇教程,我将介绍预加载服务,看看如何让你的应用"快人一步"!