云存储图片生成缩略图开发

news/2024/10/18 13:55:22
作者:狼哥
团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

注意

当前API12的端云一体化开发工程仅支持手动签名。

简介

通过此案例学习,可以学习到Serverless模板使用,云存储、云数据库、云函数;同时可以学习到如何在云函数里调用云数据库操作。

知识点

  1. 图片尺寸调整模板
  2. 云存储
  3. 云数据库
  4. 云函数

1. Serverless模板使用

使用流程

序号 步骤 详情
1 创建项目及应用 使用此Serverless模板之前,您需要先创建项目和添加应用。
2 部署模板 一键部署模板,配置模板参数,请参见部署模板。
3 使用模板 部署完成后,即可使用模板,请参见使用模板。

1.1 登录AppGallery Connect 进入到创建好的项目,开通云函数、云数据库和云存储,这里就不详细讲解如何开通云函数、云数据库、云存储,官方文档有详细讲解。

1.2 在左边菜单栏 云开发(Serverless) -> Serverless模板 -> 浏览更多Serverless模板 -> 图片尺寸调整 (点击部署) -> 选择 之前创建好的项目 -> 选择 数据处理位置 -> 配置参数 -> 开始部署 - 已部署模板

image-20240828182910803 image-20240828183043558

image-20240828183212276

image-20240828183318129

配置云函数

图片尺寸调整模板会在一键部署时自动生成模板的函数接口,模板部署成功后,您还需在“云函数”页面为对应的函数接口添加对应的云存储触发器,以实现在云存储的实例中存放图片后自动触发云函数。

1.3 选择“云开发(Serverless)> 云函数”,在“函数列表”页面根据已部署模板的“实例ID”找到模板对应的函数,点击函数名称进入函数详情页。

image-20240828220456316

1.4 在函数详情页选择“触发器”页签,点击“添加触发器”。

image-20240828220655961

1.5 在弹出的“添加触发器”窗口中配置触发器相关参数。

image-20240828220825998

具体参数说明如下表所示。

参数 说明
触发器类型 选择“云存储触发器”。
存储实例 请配置为配置云存储中保存的存储实例名称。
事件名称 选择“Completed”。

1.6 配置完成后,点击“确定”。

image-20240828220655961

小结:这样就完成了Serverless图片尺寸调整模板使用,虽然可以用逗号隔开调整生成多个尺寸不同的图片,有时我们只是想上传到不同目录下,生成的图片尺寸不同,告诉大家一个好消息,也就是可以部署多个图片尺寸调整模板,这样就可以根据不同目录,生成不同尺寸缩略图。

2. 云存储开发

2.1 文件选项是上传的文件,可以创建文件夹存放不同的文件。

image-20240828223152902

2.2 安全选项是限制上传权限,为了方便开发测试,可以临时把读写公开,如下面,方便学习此案例。

image-20240828223559534

小结:其实云存储主要设置就是安全策略,哪些文件只可以只读,哪些文件夹只可以写,哪些文件夹可以读写。

3. 云数据库开发

3.1 新增加一个图片表,用来保存上传到云存储的图片和缩略图的访问URL。

image-20240828224057204

3.2 点击新增按钮,新增对象类型,也就是数据库表。

image-20240828224325725

image-20240828224433722

image-20240828224625107

image-20240828224702615

image-20240828224758499

小结:根据图片步骤,就可以创建好t_images表,为下面云函数调用保存数据到这个表里

4. 云函数开发

4.1 云函数开发是基于端云一体化项目开发,关于端云一体化项目创建,就是在创建项目时,选择下图模板就行,前提是要先在AGC上创建了项目和应用,这里就不介绍如何创建端云一体化项目,可以移步到官方文档查看。

image-20240828225406217

4.2 右击cloudfunctions目录,创建云函数,如下图

image-20240828225751135

4.3 输入云函数名称,选择Cloud Function类型

image-20240828230034110

4.4 云函数目录结构

image-20240828230611419

4.5 云数据库操作类

const clouddb = require('@hw-agconnect/database-server/dist/index.js');
const agconnect = require('@agconnect/common-server');
const path = require('path');
import {t_images} from'./resources/t_images'/*配置区域
*/
//TODO 将AGC官网下载的配置文件放入resources文件夹下并将文件名替换为真实文件名
const credentialPath = "/resources/agc-apiclient-883106708808174848-7405487728880614016.json";
// 修改为在管理台创建的存储区名称
let zoneName = "Images"
let logger
let mCloudDBZoneexport default class CloudDBZoneWrapper {// AGC & 数据库初始化constructor(log) {logger = log;let agcClient;try {agcClient = agconnect.AGCClient.getInstance();} catch (error) {agconnect.AGCClient.initialize(agconnect.CredentialParser.toCredential(path.join(__dirname, credentialPath)));agcClient = agconnect.AGCClient.getInstance();}clouddb.AGConnectCloudDB.initialize(agcClient);const cloudDBZoneConfig = new clouddb.CloudDBZoneConfig(zoneName);const agconnectCloudDB = clouddb.AGConnectCloudDB.getInstance(agcClient);mCloudDBZone = agconnectCloudDB.openCloudDBZone(cloudDBZoneConfig);}// 写入数据,主键相同则更新async executeUpsert(data) {if (!mCloudDBZone) {console.log("CloudDBClient is null, try re-initialize it");return;}try {const resp = await mCloudDBZone.executeUpsert(data);return resp;} catch (error) {logger.info('upsertBookInfo=>', error);console.warn('upsertBookInfo=>', error)}}// 写入数据,主键相同则报错async executeInsert(data) {if (!mCloudDBZone) {console.log("CloudDBClient is null, try re-initialize it");return;}try {const resp = await mCloudDBZone.executeInsert(data);return resp;} catch (error) {logger.info('insertBookInfos=>', error);console.warn('insertBookInfos=>', error)}}// 组装需要插入或删除的数据对象getDataList(data) {let dataList = [];for(var i of data) {const unit = new t_images();unit.setId(i.id);unit.setImg_name(i.img_name);unit.setImg_big_url(i.img_big_url);unit.setImg_small_url(i.img_small_url);dataList.push(unit);}return dataList;}// 设置需要更新的主键setMainKey(mainKey) {const unit = new t_images();unit.setId(mainKey);return unit}
}

4.6 云函数操作

import CloudDBZoneWrapper from './CloudDBZoneWrapper'module.exports.myHandler = async function(event, context, callback, logger) {logger.info("event: " + JSON.stringify(event))var action;var data;const cloudDBZoneWrapper = new CloudDBZoneWrapper(logger);if (event.body) {var _body = JSON.parse(event.body);action = _body.action;data = _body.extraData;} else {action = event.action;data = event.extraData;}logger.info("data: " + JSON.stringify(data))let queryResult;switch(action) {case 'upsert':let upsertData = cloudDBZoneWrapper.getDataList(data);queryResult = await cloudDBZoneWrapper.executeUpsert(upsertData);console.log(queryResult);break;case 'insert':let insertData = cloudDBZoneWrapper.getDataList(data);queryResult = await cloudDBZoneWrapper.executeInsert(insertData);break;default:logger.info("invalid action");console.log("invalid action");}callback(queryResult);
};

5. ArkTS开发

5.1 界面UI

image-2024082823234xx image-2024082823234xx

5.2 云存储图

image-20240828232342429

5.3 云数据库表数据图

image-20240828232157512

5.4 在EntryAbility的onCreate回调函数初始化AGC

    // 初始化SDKlet input = await this.context.resourceManager.getRawFileContent('agconnect-services.json')let jsonString  = util.TextDecoder.create('utf-8', {ignoreBOM: true}).decodeWithStream(input, {stream: false});// hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate '+jsonString);initialize(this.context, JSON.parse(jsonString));

5.5 界面布局

Column() {Navigation().title($r('app.string.cloudStorage_label')).height('50vp').width('100%').margin({ bottom: 10 }).titleMode(NavigationTitleMode.Mini)Column() {Row() {Text($r('app.string.cloudStorage_description')).fontSize($r('app.float.body_font_size'))}.margin({ bottom: 15 })Row() {Button($r('app.string.cloudStorage_uploadButton'), { type: ButtonType.Normal }).borderRadius(4).width('45%').opacity(!this.isUploading ? 1 : 0.5).enabled(!this.isUploading).height(40).onClick(() => {this.upLoadImage()})Button('获取尺寸调整后URL', { type: ButtonType.Normal }).borderRadius(4).width('45%').opacity(!this.isUploading ? 1 : 0.5).enabled(!this.isUploading).height(40).onClick(() => {this.getDownloadUrl(this.smallPath)})}.width('100%').justifyContent(FlexAlign.SpaceBetween)if (this.isUploading) {Row() {Text($r('app.string.cloudStorage_progressLabel')).fontSize($r('app.float.body_font_size'))Text(`: ${this.updateProgress.toString().substring(0, 5)} %`).fontSize($r('app.float.body_font_size'))}.margin({ top: 10 })}}.alignItems(HorizontalAlign.Start).width('90%').margin({ bottom: 20 })Column() {Row() {Image(this.image).objectFit(ImageFit.Contain).height(250).backgroundColor($r('app.color.black'))}}.width('90%').margin({ bottom: 15 })}.height('100%')

5.6 打开图库选择一张图片,并把图片拷贝到缓存目录下。

private selectImage(): Promise<string> {return new Promise((resolve: (selectUri: string) => void, reject: (err: Error) => void) => {// 使用photoAccessHelper选择指定的文件let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGEphotoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目let photoViewPicker = new photoAccessHelper.PhotoViewPicker();photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {let fileUri = photoSelectResult.photoUris[0];console.info(`xx pick file ${fileUri}`);let fileName = fileUri.split('/').pop() as string;console.info(`xx file name ${fileName}`);let cacheFilePath = getContext().cacheDir + '/' + fileName;console.info(`xx cacheFilePath ${cacheFilePath}`);// 将选中文件copy至cache目录下,文件名为cacheFiletry {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);console.info(`xx 返回缓存文件路径: ${cacheFilePath}`);resolve(cacheFilePath);} catch (e) {console.info(`xx copy file failed ${e.message}`);reject(e)}});})}

5.7 上传文件到云存储

	  // localPicPath为缓存文件路径let localPicName = localPicPath.split('/').pop() as string;let imgExtension = getImageExtension(localPicName);let fileName: string = `${Date.now()}_a`;let bigPath: string = 'study/'+fileName+'.'+imgExtension;this.smallPath = 'study/thumbnail/resized_'+fileName+'144x221'+'.'+imgExtension;console.info(`xx 云存储原图路径: ${bigPath}`)// ArkUI上下文bucket.uploadFile(getContext(this), {localPath: localPicPath,  // 本地文件路径cloudPath: bigPath        // 云侧文件路径}).then((task: request.agent.Task) => {task.on('progress', (p) => {console.info(`xx on progress ${JSON.stringify(p)}`);this.updateProgress = p.processed / p.sizes[0] * 100;});task.on('completed', (progress) => {console.info(`xx on completed ${JSON.stringify(progress)}`);this.isUploading = false// 此处图片已成功上传到云存储,由于生成缩略图是异步的,此处简单处理延时10秒后,// 再获取原图和缩略图的下载URLsetTimeout(async() => {let bigUrl: string = await this.getDownloadUrl(bigPath)let smailUrl: string = await this.getDownloadUrl(this.smallPath)this.isUploading = false;// 此处封装保存到数据库表数据对象let obj: ImageObj = {id: 2,img_name: fileName,img_big_url: bigUrl,img_small_url: smailUrl}console.info(`xx 调用云函数参数:${JSON.stringify(obj)}`);// 调用自定义调用云函数方法this.callUploadImages(obj)}, 10000)});task.on('failed', (progress) => {console.error(`xx on failed ${JSON.stringify(progress)}`);this.isUploading = false});task.on('response', (response) => {console.info(`xx on response ${JSON.stringify(response)}`);});// start tasktask.start((err: BusinessError) => {if (err) {console.error(`xx Failed to start the uploadFile task, Code: ${err.code}, message: ${err.message}`);} else {console.info(`xx Succeeded in starting a uploadFile task.`);}});}).catch((err: BusinessError) => {console.error(`xx Upload file failed, Code: ${err.code}, message: ${err.message}`);});

5.8 调用云函数

  private callUploadImages(obj: ImageObj) {let arr: Array<ImageObj> = new Array<ImageObj>();arr.push(obj)let params: Params = {action: "insert",extraData: arr} as Params// 此处调用云侧云函数cloudFunction.call({ name: 'upload-images', data: params }).then((res: cloudFunction.FunctionResult) => {hilog.info(0x0000, 'CloudFunction', 'xx call upload-images, ResultMessage: %{public}s',res.result);}).catch((err: BusinessError) => {hilog.error(0x0000, 'CloudFunction', 'xx call upload-images, ErrCode: %{public}d ErrMessage: %{public}s',err.code, err.message);});}

5.9 获取图片下载URL

  private getDownloadUrl(path: string):Promise<string> {return new Promise((resolve: (selectUri: string) => void, reject: (err: Error) => void) => {bucket.getDownloadURL(path).then(async (downloadURL: string) => {hilog.info(0x0000, 'CloudStorage', 'xx DownloadURL: %{public}s', downloadURL);resolve(downloadURL);}).catch((err: BusinessError) => {hilog.error(0x0000, 'CloudStorage', 'xx getDownloadURL fail, error code: %{public}d, message: %{public}s',err.code, err.message);reject(err)});});}

5.10 获取文件名后辍

function getImageExtension(imagePath: string): string | null {// 使用正则表达式来匹配文件名中的最后一个点(.)之后的所有字符const match = imagePath.match(/\.([^.]+)$/);return match ? match[1] : null;
}

总结

此案例主要流程就是点击按钮打开图库,选择一张图片,把图片拷贝到缓存目录一下,因为目前上传文件到云存储,只支持从缓存目录下获取,图片上传到云存储后,触发图片尺寸调整云函数,生成指定尺寸缩略图,并存放到指定路径的云存储位置上,前端监听到图片上传成功后,调用获取图片下载URL接口,获取到原图和缩略图的访问URL后,调用云侧云函数,并判断出是插入数据到云数据库,从而调用云数据库保存数据,案例整体流程就是这样,覆盖到了Serverless模板使用,云存储,云函数,云数据库操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/73042.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

PHP简介与开发环境搭建

PHP简介与开发环境搭建 一、PHP简介 PHP,全称PHP: Hypertext Preprocessor(超文本预处理器),是一种广泛使用的开源服务器端脚本语言,尤其适合Web开发。PHP由Rasmus Lerdorf在1994年创建,最初是为了维护个人网页而制作的简单程序,后来逐渐发展成为功能强大的脚本语言。PH…

2153: 【例8.3】计算球的体积 球的体积公式

include <bits/stdc++.h> using namespace std; double r, pi=3.14; int main( ) { cin >> r; cout << fixed << setprecision(2)<< 4.0/3.0pirrr; return 0; } 球体是一个半圆绕直径所在直线旋转一周所成的空间几何体,简称球。球体是有且只有一…

深入理解浮点数的运算

浮点数的运算步骤 浮点数的加减运算一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断 所谓对阶是指将两个进行运算的浮点数的阶码对齐的操作。对阶的目的是为使两个浮点数的尾数能够进行加减运算。因为,当进行 $ M_{x} \times 2^{E_{x}}$与 $ M_{y} \time…

轻松上手-识图文字朗读

作者:狼哥 团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁…

mysql语法-DMLDQL

1.DML操作数据——添加、修改、删除 (1)添加数据:实例(2)修改数据实例注意:修改时如果update语句不加where条件,则会把表中所有数据都修改了! (3)删除数据:实例2.DQL查询 查询语法(1)基础查询:实例(2)条件查询:

免费使用AI写作助手,为你轻松打造爆款文章

在当今内容为王的时代,一篇高质量的文章能够迅速抓住读者的眼球,提升个人或品牌的曝光度。但对于许多创作者而言,灵感枯竭和写作效率低下是常见的挑战。此时,免费AI写作助手的出现,为解决这些问题提供了新的可能性。以下是这款AI写作助手的独特魅力和使用指南。一、AI写作…

从组合优化问题建模到贪心法求解以简单调度为例

此为课题组所指导本科生和低年级硕士生学习组合优化问题汇报 所用教材:北京大学屈婉玲教授《算法设计与分析》 课程资料:https://www.icourse163.org/course/PKU-1002525003 承诺不用于任何商业用途,仅用于学术交流和分享更多内容请关注课题组官方中文主页:https://JaywayX…

python: invalid value encountered in divide以及invalid value encountered in double_scalars报错

运行命令python eqtl_prepare_expression.py data.tpm.gct data.reads_count.gct --tpm_threshold 0.1 --count_threshold 2 --sample_frac_threshold 0.2 --normalization_method tmm --output data.txt时出现了报错“invalid value encountered in divide”以及“invalid val…