首页 > 其他分享 >云存储图片生成缩略图开发

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

时间:2024-10-18 13:43:09浏览次数:7  
标签:info 存储 console 函数 缩略图 生成 xx let 模板

作者:狼哥
团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有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 mCloudDBZone

export 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

    // 初始化SDK
    let 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; // 过滤选择媒体文件类型为IMAGE
      photoSelectOptions.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目录下,文件名为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);
          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秒后,
          // 再获取原图和缩略图的下载URL
          setTimeout(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 task
        task.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模板使用,云存储,云函数,云数据库操作。

标签:info,存储,console,函数,缩略图,生成,xx,let,模板
From: https://www.cnblogs.com/army16/p/18474095

相关文章

  • Zabbix模板数据存储在哪里?
    Zabbix的模板数据存储在数据库的哪一个表里面?以MySQL数据库为例,在数据库zabbix中,其实模板数据存储在hosts这个表里面,而不是存在hosts_templates表里面。很多人一看到templates关键字,容易先入为主的以为这个表会存储模板的相关数据。但是实际上,hosts_templates表用于存储主机和模板......
  • vue,xlsx,xlsx-style,file-saver,生成Excel并导出,cptable报错,合并单元格 样式缺失
    一,安装依赖 二,导入依赖import*asXLSXfrom'xlsx';import*asXLSX_STYLEfrom'xlsx-style'import{saveAs}from'file-saver';三,解决引入xlsx-style./cptable模块找不到问题Thisrelativemodulewasnotfound:*./cptablein./node_modules......
  • lintsampler:高效从任意概率分布生成随机样本的新方法
    在实际应用中,我们经常需要从给定的概率密度函数(PDF)中抽取随机样本。这种需求在多个领域都很常见,例如:估计统计量进行蒙特卡洛模拟生成粒子系统用于物理仿真对于标准概率分布,如均匀分布或高斯分布(正态分布),numpy和scipy生态系统提供了现成的解决方案。通过numpy.rand......
  • ubuntu .c编译生成.so
    在Ubuntu上,将.c文件编译成.so(共享对象库)文件,通常是编译生成共享库。打开终端,并使用gcc来编译你的.c文件,生成共享库。1.gcc-shared-olibexample.soexample.c这条命令将会编译example.c并生成一个名为libexample.so的共享库。 2.如果你的.c文件依赖其他的.c文件或者库,你......
  • 最小生成树(Minimum Spanning Tree,MST)初步
    定义连通图的最小生成树(MinimumSpanningTree,MST)为边权和最小的生成树。注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。思路分为Kruskal与Prim两种算法。Kruskal从最小边权的边开始,按边权从小到大依次遍历。若当前边连接的两点不连通,加入此边。Prim每次选......
  • k8s-Longhorn系统配置 20241017 -分布式存储
    目录一Longhorn存储部署1.1Longhorn概述1.2Longhorn部署1.5动态sc创建1.6测试PV及PVC1.7Ingress暴露Longhorn1.8确认验证附加Helm部署附0.1helm安装附0.2helm安装 回到顶部一Longhorn存储部署1.1Longhorn概述Longhorn是用于Kubernetes的......
  • 雪花算法------用于生成数据库中的主键、消息队列的消息ID等的算法-----算法特点,id结
    雪花算法(SnowflakeAlgorithm)是一种由Twitter公司开发的分布式ID生成算法,用于在分布式系统中生成全局唯一的ID。这种算法非常适合需要高并发、低延迟以及大量唯一ID生成的应用场景,比如数据库中的主键、消息队列的消息ID等。雪花算法的主要特点包括:唯一性:生成的ID在全球范围内......
  • 17.Python基础篇-闭包、装饰器、迭代器、生成器
    函数的进阶—闭包闭包的定义:嵌套函数,内部函数调用外部函数的变量。满足这个条件就算闭包。闭包案例演示:defouter():a=1definner():print('inner函数中打印的变量a:',a)#嵌套函数中使用了外层函数的变量。此时满足了闭包的条件。returninner......
  • 图论中的最小生成树算法
    错题考察的知识点是图论中的最小生成树算法,特别是Prim算法和Kruskal算法。这两种算法都是用来寻找无向连通图中的最小生成树的。最小生成树是指连接图中所有顶点的边的集合,且这些边的总权重最小,同时保证任意两个顶点之间都是连通的。Prim算法:原理:从一个任意顶点开始,逐步增加新......
  • Next.js 深度教程:服务端渲染、静态生成到增量静态再生 | 2024最新版
    优化字体和图像书接上回,我们学习了如何设计Next.js应用程序,让我们继续优化主页和添加自定义字体、图像。在网站设计中,字体扮演着关键角色,然而,若需获取并加载字体文件,项目中引入自定义字体可能对性能产生影响。Google采用累计布局偏移(CLS)作为评估网站性能和用户体验的指标。对......